diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..ddd6d2d5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,14 @@ +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2016-Present Datadog, Inc. + +[*.{kt,kts}] +ktlint_code_style = android_studio +ij_kotlin_allow_trailing_comma_on_call_site = false +ij_kotlin_allow_trailing_comma = false +ij_kotlin_imports_layout=*,java.**,javax.**,kotlin.**,^ +max_line_length = 120 + +# SPDX License Names +[buildSrc/src/main/kotlin/com/datadog/gradle/plugin/checklicenses/SPDXLicense.kt] +ktlint_standard_enum-entry-name-case = disabled diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 96844014..4df40bf8 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -48,7 +48,7 @@ jobs: - name: Setup Gradle uses: gradle/gradle-build-action@v2.4.2 with: - gradle-version: 8.6 + gradle-version: 8.7 # Manually build the java bytecode - name: Execute Gradle build diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3903533f..1b2ed3fe 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,16 +4,19 @@ include: # SETUP variables: - CURRENT_CI_IMAGE: "5" + CURRENT_CI_IMAGE: "8" CI_IMAGE_DOCKER: 486234852809.dkr.ecr.us-east-1.amazonaws.com/ci/dd-sdk-android-gradle-plugin:$CURRENT_CI_IMAGE GIT_DEPTH: 5 - DD_AGENT_HOST: "$BUILDENV_HOST_IP" DD_SERVICE: "dd-sdk-android-gradle-plugin" DD_ENV_TESTS: "ci" DD_INTEGRATION_JUNIT_5_ENABLED: "true" DD_CIVISIBILITY_ENABLED: "true" DD_INSIDE_CI: "true" + DD_COMMON_AGENT_CONFIG: "dd.env=ci,dd.trace.enabled=false,dd.jmx.fetch.enabled=false" + + KUBERNETES_MEMORY_REQUEST: "8Gi" + KUBERNETES_MEMORY_LIMIT: "16Gi" stages: - ci-image @@ -29,18 +32,17 @@ ci-image: stage: ci-image when: manual except: [ tags, schedules ] - tags: [ "runner:docker" ] - image: 486234852809.dkr.ecr.us-east-1.amazonaws.com/docker:20.10.13 + tags: [ "arch:amd64" ] + image: 486234852809.dkr.ecr.us-east-1.amazonaws.com/docker:24.0.4-gbi-focal script: - - docker build --tag $CI_IMAGE_DOCKER -f Dockerfile.gitlab . - - docker push $CI_IMAGE_DOCKER + - docker buildx build --tag $CI_IMAGE_DOCKER -f Dockerfile.gitlab --push . # SECURITY create_key: stage: security when: manual - tags: [ "runner:docker" ] + tags: [ "arch:amd64" ] variables: PROJECT_NAME: "dd-sdk-android-gradle-plugin" EXPORT_TO_KEYSERVER: "true" @@ -59,11 +61,11 @@ static-analysis: variables: DETEKT_PUBLIC_API: "true" trigger: - include: "https://gitlab-templates.ddbuild.io/mobile/v13284631-3b5f5110/static-analysis.yml" + include: "https://gitlab-templates.ddbuild.io/mobile/v25558398-8517309a/static-analysis.yml" strategy: depend analysis:licenses: - tags: [ "runner:main" ] + tags: [ "arch:amd64" ] image: $CI_IMAGE_DOCKER stage: analysis timeout: 30m @@ -72,7 +74,7 @@ analysis:licenses: - GRADLE_OPTS="-Xmx2560m" ./gradlew :dd-sdk-android-gradle-plugin:checkThirdPartyLicences --stacktrace --no-daemon analysis:woke: - tags: [ "runner:main" ] + tags: [ "arch:amd64" ] image: $CI_IMAGE_DOCKER stage: analysis timeout: 30m @@ -83,7 +85,7 @@ analysis:woke: # TESTS test:plugin: - tags: [ "runner:main" ] + tags: [ "arch:amd64" ] image: $CI_IMAGE_DOCKER stage: test timeout: 1h @@ -91,7 +93,8 @@ test:plugin: - git fetch --depth=1 origin main - rm -rf ~/.gradle/daemon/ - CODECOV_TOKEN=$(aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android-gradle-plugin.codecov-token --with-decryption --query "Parameter.Value" --out text) - - GRADLE_OPTS="-Xmx2560m" ./gradlew :dd-sdk-android-gradle-plugin:test --stacktrace --no-daemon + - export DD_AGENT_HOST="$BUILDENV_HOST_IP" + - GRADLE_OPTS="-Xmx2560m" DD_TAGS="test.module:dd-sdk-android-gradle-plugin" ./gradlew :dd-sdk-android-gradle-plugin:test --stacktrace --no-daemon -Dorg.gradle.jvmargs=-javaagent:$(pwd)/libs/dd-java-agent-1.26.1.jar=$DD_COMMON_AGENT_CONFIG - bash <(cat ./codecov.sh) -t $CODECOV_TOKEN artifacts: reports: @@ -100,7 +103,7 @@ test:plugin: # PUBLISH ARTIFACTS publish:publish-sonatype: - tags: [ "runner:main" ] + tags: [ "arch:amd64" ] only: - tags image: $CI_IMAGE_DOCKER @@ -116,7 +119,7 @@ publish:publish-sonatype: - ./gradlew :dd-sdk-android-gradle-plugin:publishPluginMavenPublicationToMavenRepository --stacktrace --no-daemon publish:publish-gradle-portal: - tags: [ "runner:main" ] + tags: [ "arch:amd64" ] only: - tags image: $CI_IMAGE_DOCKER @@ -140,7 +143,7 @@ notify:release: script: - MAVEN_URL="https://search.maven.org/artifact/com.datadoghq/dd-sdk-android-gradle-plugin/$CI_COMMIT_TAG/aar" - 'MESSAGE_TEXT=":package: $CI_PROJECT_NAME $CI_COMMIT_TAG published on :maven: $MAVEN_URL"' - - postmessage "#mobile-rum" "$MESSAGE_TEXT" + - postmessage "#mobile-sdk-ops" "$MESSAGE_TEXT" notify:failure: extends: .slack-notifier-base @@ -151,4 +154,45 @@ notify:failure: script: - BUILD_URL="$CI_PROJECT_URL/pipelines/$CI_PIPELINE_ID" - 'MESSAGE_TEXT=":status_alert: $CI_PROJECT_NAME $CI_COMMIT_TAG publish pipeline <$BUILD_URL|$COMMIT_MESSAGE> failed."' - - postmessage "#mobile-rum" "$MESSAGE_TEXT" + - postmessage "#mobile-sdk-ops" "$MESSAGE_TEXT" + +# DOGFOOD + +notify:dogfood-app: + tags: [ "arch:amd64" ] + only: + - tags + image: $CI_IMAGE_DOCKER + stage: notify + when: on_success + script: + - pip3 install GitPython requests + # reuse service account + - aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.gh_token --with-decryption --query "Parameter.Value" --out text >> ./gh_token + - python3 dogfood.py -v $CI_COMMIT_TAG -t app + +notify:dogfood-demo: + tags: [ "arch:amd64" ] + only: + - tags + image: $CI_IMAGE_DOCKER + stage: notify + when: on_success + script: + - pip3 install GitPython requests + # reuse service account + - aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.gh_token --with-decryption --query "Parameter.Value" --out text >> ./gh_token + - python3 dogfood.py -v $CI_COMMIT_TAG -t demo + +notify:dogfood-gradle-plugin: + tags: [ "arch:amd64" ] + only: + - tags + image: $CI_IMAGE_DOCKER + stage: notify + when: on_success + script: + - pip3 install GitPython requests + # reuse service account + - aws ssm get-parameter --region us-east-1 --name ci.dd-sdk-android.gh_token --with-decryption --query "Parameter.Value" --out text >> ./gh_token + - python3 dogfood.py -v $CI_COMMIT_TAG -t gradle-plugin diff --git a/CHANGELOG.md b/CHANGELOG.md index 82accc76..5f735d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,31 @@ +# 1.14.0 / 2024-05-30 + +* [FEATURE] Add tasks for upload NDK symbol files to Datadog. See [#220](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/220) +* [FEATURE] Support new Variant API. See [#263](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/263) +* [IMPROVEMENT] Standardize architectures. See [#249](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/249) +* [IMPROVEMENT] Emulate upload network call for functional tests. See [#255](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/255) +* [IMPROVEMENT] Mark upload task as not compatible with configuration cache. See [#256](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/256) +* [IMPROVEMENT] Avoid BuildId task creation if there is no obfuscation or native build providers registered. See [#259](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/259) +* [IMPROVEMENT] Increase upload timeout to 120s to match EvP limits. See [#261](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/261) +* [IMPROVEMENT] Add test for the duplicate resources issue with AGP 8.4.x. See [#264](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/264) +* [IMPROVEMENT] Use mapping file provider in case of legacy Variant API. See [#265](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/265) +* [MAINTENANCE] Next dev iteration. See [#233](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/233) +* [MAINTENANCE] Update version of static analysis pipeline used. See [#234](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/234) +* [MAINTENANCE] Update Datadog SDK to version 2.8.0. See [#235](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/235) +* [MAINTENANCE] Merge `release/1.13.0` branch into `develop` branch. See [#236](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/236) +* [MAINTENANCE] Use Datadog Gradle Plugin 1.13.0 in samples. See [#238](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/238) +* [MAINTENANCE] Update AGP to 8.3.2. See [#239](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/239) +* [MAINTENANCE] Merge release `1.13.1` into `develop` branch. See [#244](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/244) +* [MAINTENANCE] Use Datadog Gradle Plugin 1.13.1 for samples. See [#245](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/245) +* [MAINTENANCE] Migrate to GBI images. See [#246](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/246) +* [MAINTENANCE] Add dogfood script. See [#247](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/247) +* [MAINTENANCE] Install NDK 25.1.8937393 in the Docker image. See [#248](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/248) +* [MAINTENANCE] Redirect slack notifications to `mobile-sdk-ops` channel. See [#250](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/250) +* [MAINTENANCE] Update Gradle to 8.7, AGP to 8.4.0. See [#253](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/253) +* [MAINTENANCE] Update Datadog SDK to version 2.9.0. See [#254](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/254) +* [MAINTENANCE] Update AGP version to 8.4.1. See [#260](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/260) +* [MAINTENANCE] Update Datadog SDK to version 2.10.0. See [#262](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/262) + # 1.13.1 / 2024-04-11 * [BUGFIX] Avoid eager fetching of Variant values. See [#240](https://github.com/DataDog/dd-sdk-android-gradle-plugin/pull/240) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc98976e..ccf728e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -27,9 +27,6 @@ The whole project is covered by a set of static analysis tools, linters and test # launches the unit tests ./gradlew :dd-sdk-android-gradle-plugin:test -# launches the detekt static analysis -./gradlew :dd-sdk-android-gradle-plugin:detekt - # launches the ktlint check and formatter for all Kotlin files (the ktlint client needs to be installed on your machine) ktlint -F "**/*.kt" "**/*.kts" '!**/build/generated/**' '!**/build/kspCaches/**' diff --git a/Dockerfile.gitlab b/Dockerfile.gitlab index 9d9dc399..2459e667 100644 --- a/Dockerfile.gitlab +++ b/Dockerfile.gitlab @@ -1,4 +1,4 @@ -FROM registry.ddbuild.io/images/mirror/ubuntu:20.04 +FROM registry.ddbuild.io/images/docker:24.0.4-gbi-focal ENV DEBIAN_FRONTEND=noninteractive @@ -24,11 +24,11 @@ RUN set -x \ && apt-get -y clean \ && rm -rf /var/lib/apt/lists/* -ENV GRADLE_VERSION 8.6 +ENV GRADLE_VERSION 8.7 ENV ANDROID_COMPILE_SDK 34 ENV ANDROID_BUILD_TOOLS 34.0.0 ENV ANDROID_SDK_TOOLS 11076708 -ENV NDK_VERSION 21.3.6528147 +ENV NDK_VERSION 26.1.10909125 ENV CMAKE_VERSION 3.22.1 @@ -70,7 +70,7 @@ RUN \ echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager "build-tools;${ANDROID_BUILD_TOOLS}" >/dev/null && \ echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --install "ndk;${NDK_VERSION}" >/dev/null && \ echo y | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --install "cmake;${CMAKE_VERSION}" >/dev/null && \ - yes | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --licenses + (yes || true) | android-sdk-linux/cmdline-tools/latest/bin/sdkmanager --licenses RUN set -x \ && curl -OL https://s3.amazonaws.com/dd-package-public/dd-package.deb && dpkg -i dd-package.deb && rm dd-package.deb \ diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 3134d6ba..85697b73 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -31,7 +31,6 @@ dependencies { // Dependencies used to configure the gradle plugins implementation(embeddedKotlin("gradle-plugin")) - implementation(libs.detektPluginGradle) implementation(libs.androidToolsPluginGradle) implementation(libs.versionsPluginGradle) implementation(libs.fuzzyWuzzy) diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektConfig.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektConfig.kt deleted file mode 100644 index 98adb28c..00000000 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/config/DetektConfig.kt +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2020-Present Datadog, Inc. - */ - -package com.datadog.gradle.config - -import io.gitlab.arturbosch.detekt.extensions.DetektExtension -import org.gradle.api.Project - -fun Project.detektConfig() { - extensionConfig { - input = files("$projectDir/src/main/kotlin") - config = files("${project.rootDir}/detekt.yml") - - reports { - xml { - enabled = true - destination = file("build/reports/detekt.xml") - } - } - } - - tasks.named("check") { - dependsOn("detekt") - } -} diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt index a5ec87af..e06a3d5f 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/config/KotlinConfig.kt @@ -8,9 +8,7 @@ package com.datadog.gradle.config import org.gradle.api.JavaVersion import org.gradle.api.Project -import org.gradle.api.tasks.testing.Test import org.jetbrains.kotlin.gradle.tasks.KotlinCompile -import java.io.File fun Project.kotlinConfig() { taskConfig { @@ -19,22 +17,4 @@ fun Project.kotlinConfig() { jvmTarget = JavaVersion.VERSION_11.toString() } } - - val moduleName = this@kotlinConfig.name - val javaAgentJar = File(File(rootDir, "libs"), "dd-java-agent-0.98.1.jar") - taskConfig { - if (environment["DD_INTEGRATION_JUNIT_5_ENABLED"] == "true") { - // set the `env` tag for the test spans - environment("DD_ENV", "ci") - // add custom tags based on the module and variant (debug/release, flavors, …) - environment("DD_TAGS", "test.module:$moduleName") - - // disable other Datadog integrations that could interact with the Java Agent - environment("DD_INTEGRATIONS_ENABLED", "false") - // disable JMX integration - environment("DD_JMX_FETCH_ENABLED", "false") - - jvmArgs("-javaagent:${javaAgentJar.absolutePath}") - } - } } diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/config/MavenConfig.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/config/MavenConfig.kt index df27d396..28b9f175 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/config/MavenConfig.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/config/MavenConfig.kt @@ -13,19 +13,16 @@ import org.gradle.api.publish.PublishingExtension import org.gradle.api.publish.maven.MavenPublication import org.gradle.jvm.tasks.Jar import org.gradle.kotlin.dsl.findByType -import org.gradle.kotlin.dsl.get -import org.gradle.kotlin.dsl.named import org.gradle.plugins.signing.SigningExtension import java.net.URI object MavenConfig { - val VERSION = Version(1, 13, 1, Version.Type.Release) + val VERSION = Version(1, 14, 0, Version.Type.Release) const val GROUP_ID = "com.datadoghq" const val PUBLICATION = "pluginMaven" } -@Suppress("UnstableApiUsage") fun Project.publishingConfig(projectDescription: String) { val projectName = name val signingExtension = extensions.findByType(SigningExtension::class) diff --git a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/checklicenses/SPDXLicense.kt b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/checklicenses/SPDXLicense.kt index 9ae6347d..0debe33d 100644 --- a/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/checklicenses/SPDXLicense.kt +++ b/buildSrc/src/main/kotlin/com/datadog/gradle/plugin/checklicenses/SPDXLicense.kt @@ -6,7 +6,7 @@ package com.datadog.gradle.plugin.checklicenses -@Suppress("ktlint:enum-entry-name-case", "EnumNaming") +@Suppress("ktlint:standard:enum-entry-name-case", "EnumNaming") enum class SPDXLicense(val csvName: String) { _0BSD("0BSD"), AAL("AAL"), diff --git a/dd-sdk-android-gradle-plugin/build.gradle.kts b/dd-sdk-android-gradle-plugin/build.gradle.kts index bf6b8870..a4cc0087 100644 --- a/dd-sdk-android-gradle-plugin/build.gradle.kts +++ b/dd-sdk-android-gradle-plugin/build.gradle.kts @@ -6,7 +6,6 @@ import com.datadog.gradle.config.MavenConfig import com.datadog.gradle.config.dependencyUpdateConfig -import com.datadog.gradle.config.detektConfig import com.datadog.gradle.config.jacocoConfig import com.datadog.gradle.config.javadocConfig import com.datadog.gradle.config.junitConfig @@ -26,7 +25,6 @@ plugins { // Analysis tools id("com.github.ben-manes.versions") - id("io.gitlab.arturbosch.detekt") // Tests jacoco @@ -48,11 +46,9 @@ dependencies { testImplementation(libs.okHttpMock) testImplementation(libs.androidToolsPluginGradle) testImplementation(libs.kotlinPluginGradle) - detekt(libs.detektCli) } kotlinConfig() -detektConfig() junitConfig() jacocoConfig() javadocConfig() diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdCheckSdkDepsTask.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/CheckSdkDepsTask.kt similarity index 97% rename from dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdCheckSdkDepsTask.kt rename to dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/CheckSdkDepsTask.kt index 5d61f096..7768e55f 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdCheckSdkDepsTask.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/CheckSdkDepsTask.kt @@ -21,7 +21,7 @@ import java.util.Queue /** * A Gradle task to check the Datadog SDK throughout the variant dependencies. */ -abstract class DdCheckSdkDepsTask : DefaultTask() { +abstract class CheckSdkDepsTask : DefaultTask() { /** * The sdkCheckLevel: NONE, WARN, FAIL. @@ -47,7 +47,7 @@ abstract class DdCheckSdkDepsTask : DefaultTask() { init { group = DdAndroidGradlePlugin.DATADOG_TASK_GROUP description = "Checks for the Datadog SDK into your variant dependencies." - outputs.upToDateWhen { it is DdCheckSdkDepsTask && it.isLastRunSuccessful } + outputs.upToDateWhen { it is CheckSdkDepsTask && it.isLastRunSuccessful } } /** diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePlugin.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePlugin.kt index 4d0f27f9..8769e1de 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePlugin.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePlugin.kt @@ -6,16 +6,23 @@ package com.datadog.gradle.plugin +import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.gradle.AppExtension -import com.android.build.gradle.api.ApplicationVariant import com.datadog.gradle.plugin.internal.ApiKey import com.datadog.gradle.plugin.internal.ApiKeySource +import com.datadog.gradle.plugin.internal.CurrentAgpVersion import com.datadog.gradle.plugin.internal.GitRepositoryDetector import com.datadog.gradle.plugin.internal.VariantIterator +import com.datadog.gradle.plugin.internal.lazyBuildIdProvider +import com.datadog.gradle.plugin.internal.variant.AppVariant +import com.datadog.gradle.plugin.internal.variant.NewApiAppVariant import org.gradle.api.Plugin import org.gradle.api.Project import org.gradle.api.Task +import org.gradle.api.file.RegularFile import org.gradle.api.logging.Logging +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.TaskContainer import org.gradle.api.tasks.TaskProvider @@ -43,17 +50,29 @@ class DdAndroidGradlePlugin @Inject constructor( // need to use withPlugin instead of afterEvaluate, because otherwise generated assets // folder with buildId is not picked by AGP by some reason target.pluginManager.withPlugin("com.android.application") { - val androidExtension = target.androidApplicationExtension ?: return@withPlugin - androidExtension.applicationVariants.all { variant -> - if (extension.enabled) { + if (CurrentAgpVersion.CAN_ENABLE_NEW_VARIANT_API && !target.hasProperty(DD_FORCE_LEGACY_VARIANT_API) + ) { + val androidComponentsExtension = target.androidApplicationComponentExtension ?: return@withPlugin + androidComponentsExtension.onVariants { variant -> configureTasksForVariant( target, - androidExtension, extension, - variant, + AppVariant.create(variant, target), apiKey ) } + } else { + val androidExtension = target.androidApplicationExtension ?: return@withPlugin + androidExtension.applicationVariants.all { variant -> + if (extension.enabled) { + configureTasksForVariant( + target, + extension, + AppVariant.create(variant, androidExtension, target), + apiKey + ) + } + } } } @@ -73,32 +92,53 @@ class DdAndroidGradlePlugin @Inject constructor( internal fun configureTasksForVariant( target: Project, - androidExtension: AppExtension, datadogExtension: DdExtension, - variant: ApplicationVariant, + variant: AppVariant, apiKey: ApiKey ) { - val buildIdGenerationTask = - configureBuildIdGenerationTask(target, androidExtension, variant) - - if (isObfuscationEnabled(variant, datadogExtension)) { - configureVariantForUploadTask( - target, - variant, - buildIdGenerationTask, - apiKey, - datadogExtension - ) + val isObfuscationEnabled = isObfuscationEnabled(variant, datadogExtension) + val isNativeBuildRequired = variant.isNativeBuildEnabled + + if (isObfuscationEnabled || isNativeBuildRequired) { + val buildIdGenerationTask = + configureBuildIdGenerationTask(target, variant) + + if (isObfuscationEnabled) { + configureVariantForUploadTask( + target, + variant, + buildIdGenerationTask, + apiKey, + datadogExtension + ) + } else { + LOGGER.info("Minifying disabled for variant ${variant.name}, no mapping file upload task created") + } + + if (isNativeBuildRequired) { + configureNdkSymbolUploadTask( + target, + datadogExtension, + variant, + buildIdGenerationTask, + apiKey + ) + } else { + LOGGER.info( + "No native build tasks found for variant ${variant.name}," + + " no NDK symbol file upload task created." + ) + } + } + + if (variant is NewApiAppVariant) { + // need to run this in afterEvaluate, because with new Variant API tasks won't be created yet at this point + target.afterEvaluate { + configureVariantForSdkCheck(target, variant, datadogExtension) + } } else { - LOGGER.info("Minifying disabled for variant ${variant.name}, no upload task created") + configureVariantForSdkCheck(target, variant, datadogExtension) } - configureNdkSymbolUploadTask( - target, - datadogExtension, - variant, - buildIdGenerationTask - ) - configureVariantForSdkCheck(target, variant, datadogExtension) } @Suppress("ReturnCount") @@ -114,16 +154,16 @@ class DdAndroidGradlePlugin @Inject constructor( return apiKey ?: ApiKey.NONE } - internal fun configureNdkSymbolUploadTask( + private fun configureNdkSymbolUploadTask( target: Project, extension: DdExtension, - variant: ApplicationVariant, - buildIdTask: TaskProvider - ): TaskProvider? { - val apiKey = resolveApiKey(target) + variant: AppVariant, + buildIdTask: TaskProvider, + apiKey: ApiKey + ): TaskProvider { val extensionConfiguration = resolveExtensionConfiguration(extension, variant) - val uploadTask = DdNdkSymbolFileUploadTask.register( + val uploadTask = NdkSymbolFileUploadTask.register( target, variant, buildIdTask, @@ -137,96 +177,56 @@ class DdAndroidGradlePlugin @Inject constructor( } @Suppress("StringLiteralDuplication") - internal fun configureBuildIdGenerationTask( + private fun configureBuildIdGenerationTask( target: Project, - appExtension: AppExtension, - variant: ApplicationVariant + variant: AppVariant ): TaskProvider { val buildIdDirectory = target.layout.buildDirectory .dir(Path("generated", "datadog", "buildId", variant.name).toString()) val buildIdGenerationTask = GenerateBuildIdTask.register(target, variant, buildIdDirectory) - // we could generate buildIdDirectory inside GenerateBuildIdTask and read it here as - // property using flatMap, but when Gradle sync is done inside Android Studio there is an error - // Querying the mapped value of provider (java.util.Set) before task ... has completed is - // not supported, which doesn't happen when Android Studio is not used (pure Gradle build) - // so applying such workaround - // TODO RUM-0000 use new AndroidComponents API to inject generated stuff, it is more friendly - appExtension.sourceSets.getByName(variant.name).assets.srcDir(buildIdDirectory) - - val variantName = variant.name.capitalize() - listOf( - "package${variantName}Bundle", - "build${variantName}PreBundle", - "lintVitalAnalyze$variantName", - "lintVitalReport$variantName", - "generate${variantName}LintVitalReportModel" - ).forEach { - target.tasks.findByName(it)?.dependsOn(buildIdGenerationTask) - } - - // don't merge these 2 into list to call forEach, because common superclass for them - // is different between AGP versions, which may cause ClassCastException - variant.mergeAssetsProvider.configure { it.dependsOn(buildIdGenerationTask) } - variant.packageApplicationProvider.configure { it.dependsOn(buildIdGenerationTask) } - return buildIdGenerationTask } @Suppress("DefaultLocale", "ReturnCount") internal fun configureVariantForUploadTask( target: Project, - variant: ApplicationVariant, + variant: AppVariant, buildIdGenerationTask: TaskProvider, apiKey: ApiKey, extension: DdExtension - ): TaskProvider { + ): TaskProvider { val uploadTaskName = UPLOAD_TASK_NAME + variant.name.capitalize() val uploadTask = target.tasks.register( uploadTaskName, - DdMappingFileUploadTask::class.java, + MappingFileUploadTask::class.java, GitRepositoryDetector(execOps) ).apply { configure { uploadTask -> - val extensionConfiguration = resolveExtensionConfiguration(extension, variant) - configureVariantTask(uploadTask, apiKey, variant.flavorName, extensionConfiguration, variant) - - // upload task shouldn't depend on the build ID generation task, but only read its property, - // because upload task may be triggered after assemble task and we don't want to re-generate - // build ID, because it will be different then from the one which is already embedded in - // the application package - uploadTask.buildId = buildIdGenerationTask.flatMap { - it.buildIdFile.flatMap { - providerFactory.provider { it.asFile.readText().trim() } - } - } - uploadTask.mappingFilePath = resolveMappingFilePath(extensionConfiguration, target, variant) - uploadTask.mappingFilePackagesAliases = - filterMappingFileReplacements( - extensionConfiguration.mappingFilePackageAliases, - variant.applicationId + @Suppress("MagicNumber") + if (TaskUtils.isGradleAbove(target, 7, 5)) { + uploadTask.notCompatibleWithConfigurationCache( + "Datadog Upload Mapping task is not" + + " compatible with configuration cache yet." ) - uploadTask.mappingFileTrimIndents = extensionConfiguration.mappingFileTrimIndents - if (!extensionConfiguration.ignoreDatadogCiFileConfig) { - uploadTask.datadogCiFile = DdTaskUtils.findDatadogCiFile(target.projectDir) } + val extensionConfiguration = resolveExtensionConfiguration(extension, variant) + configureVariantTask( + target.objects, + uploadTask, + apiKey, + extensionConfiguration, + variant + ) - uploadTask.repositoryFile = DdTaskUtils.resolveDatadogRepositoryFile(target) - - val roots = mutableListOf() - variant.sourceSets.forEach { - roots.addAll(it.javaDirectories) - @Suppress("MagicNumber") - if (DdTaskUtils.isAgpAbove(7, 0, 0)) { - roots.addAll(it.kotlinDirectories) - } + uploadTask.buildId.set(buildIdGenerationTask.lazyBuildIdProvider(providerFactory)) + uploadTask.mappingFilePackagesAliases = extensionConfiguration.mappingFilePackageAliases + uploadTask.mappingFileTrimIndents = extensionConfiguration.mappingFileTrimIndents + if (!extensionConfiguration.ignoreDatadogCiFileConfig) { + uploadTask.datadogCiFile = TaskUtils.findDatadogCiFile(target.projectDir) } - // it can be an overlap between java and kotlin directories and since File doesn't override - // equals for set comparison, we will remove duplicates manually - uploadTask.sourceSetRoots = roots.map { it.absolutePath } - .distinct() - .map { File(it) } + uploadTask.repositoryFile = TaskUtils.resolveDatadogRepositoryFile(target) } } @@ -236,9 +236,9 @@ class DdAndroidGradlePlugin @Inject constructor( @Suppress("ReturnCount") internal fun configureVariantForSdkCheck( target: Project, - variant: ApplicationVariant, + variant: AppVariant, extension: DdExtension - ): TaskProvider? { + ): TaskProvider? { if (!extension.enabled) { LOGGER.info("Extension disabled for variant ${variant.name}, no sdk check task created") return null @@ -268,7 +268,7 @@ class DdAndroidGradlePlugin @Inject constructor( extensionConfiguration.checkProjectDependencies ?: SdkCheckLevel.FAIL val checkDepsTaskProvider = target.tasks.register( checkDepsTaskName, - DdCheckSdkDepsTask::class.java + CheckSdkDepsTask::class.java ) { it.configurationName.set(variant.compileConfiguration.name) it.sdkCheckLevel.set(resolvedCheckDependencyFlag) @@ -282,7 +282,7 @@ class DdAndroidGradlePlugin @Inject constructor( @Suppress("DefaultLocale") private fun findCompilationTask( taskContainer: TaskContainer, - appVariant: ApplicationVariant + appVariant: AppVariant ): Task? { // variants will have name like proDebug, but compile task will have a name like // compileProDebugSources. It can be other tasks like compileProDebugAndroidTestSources @@ -297,69 +297,61 @@ class DdAndroidGradlePlugin @Inject constructor( ?: taskContainer.findByName("compile${appVariant.name.capitalize()}Sources") } - private fun resolveMappingFilePath( + private fun resolveMappingFile( extensionConfiguration: DdExtensionConfiguration, - target: Project, - variant: ApplicationVariant - ): String { + objectFactory: ObjectFactory, + variant: AppVariant + ): Provider { val customPath = extensionConfiguration.mappingFilePath return if (customPath != null) { - customPath + objectFactory.fileProperty().fileValue(File(customPath)) } else { - val outputsDir = File(target.buildDir, "outputs") - val mappingDir = File(outputsDir, "mapping") - val flavorDir = File(mappingDir, variant.name) - File(flavorDir, "mapping.txt").path - } - } - - private fun filterMappingFileReplacements( - replacements: Map, - applicationId: String - ): Map { - return replacements.filter { - // not necessarily applicationId == package attribute from the Manifest, but it is - // best and cheapest effort to avoid wrong renaming (otherwise we may loose Git - // integration feature). - if (applicationId.startsWith(it.key)) { - LOGGER.warn( - "Renaming of package prefix=${it.key} will be skipped, because" + - " it belongs to the application package." - ) - false - } else { - true - } + variant.mappingFile } } private fun configureVariantTask( - uploadTask: DdMappingFileUploadTask, + objectFactory: ObjectFactory, + uploadTask: MappingFileUploadTask, apiKey: ApiKey, - flavorName: String, extensionConfiguration: DdExtensionConfiguration, - variant: ApplicationVariant + variant: AppVariant ) { uploadTask.apiKey = apiKey.value uploadTask.apiKeySource = apiKey.source - uploadTask.variantName = flavorName + uploadTask.variantName = variant.flavorName + + uploadTask.applicationId.set(variant.applicationId) + + uploadTask.mappingFile.set(resolveMappingFile(extensionConfiguration, objectFactory, variant)) + uploadTask.sourceSetRoots.set(variant.collectJavaAndKotlinSourceDirectories()) uploadTask.site = extensionConfiguration.site ?: "" - uploadTask.versionName = extensionConfiguration.versionName ?: variant.versionName - uploadTask.versionCode = providerFactory.provider { variant.versionCode } - uploadTask.serviceName = extensionConfiguration.serviceName ?: variant.applicationId + if (extensionConfiguration.versionName != null) { + uploadTask.versionName.set(extensionConfiguration.versionName) + } else { + uploadTask.versionName.set(variant.versionName) + } + uploadTask.versionCode.set(variant.versionCode) + if (extensionConfiguration.serviceName != null) { + uploadTask.serviceName.set(extensionConfiguration.serviceName) + } else { + uploadTask.serviceName.set(variant.applicationId) + } uploadTask.remoteRepositoryUrl = extensionConfiguration.remoteRepositoryUrl ?: "" + + variant.bindWith(uploadTask) } internal fun resolveExtensionConfiguration( extension: DdExtension, - variant: ApplicationVariant + variant: AppVariant ): DdExtensionConfiguration { val configuration = DdExtensionConfiguration() configuration.updateWith(extension) - val flavors = variant.productFlavors.map { it.name } - val buildType = variant.buildType.name + val flavors = variant.flavors + val buildType = variant.buildTypeName val iterator = VariantIterator(flavors + buildType) iterator.forEach { val config = extension.variants.findByName(it) @@ -375,11 +367,11 @@ class DdAndroidGradlePlugin @Inject constructor( } private fun isObfuscationEnabled( - variant: ApplicationVariant, + variant: AppVariant, extension: DdExtension ): Boolean { val extensionConfiguration = resolveExtensionConfiguration(extension, variant) - val isDefaultObfuscationEnabled = variant.buildType.isMinifyEnabled + val isDefaultObfuscationEnabled = variant.isMinifyEnabled val isNonDefaultObfuscationEnabled = extensionConfiguration.nonDefaultObfuscation return isDefaultObfuscationEnabled || isNonDefaultObfuscationEnabled } @@ -387,10 +379,15 @@ class DdAndroidGradlePlugin @Inject constructor( private val Project.androidApplicationExtension: AppExtension? get() = extensions.findByType(AppExtension::class.java) + private val Project.androidApplicationComponentExtension: ApplicationAndroidComponentsExtension? + get() = extensions.findByType(ApplicationAndroidComponentsExtension::class.java) + // endregion companion object { + private const val DD_FORCE_LEGACY_VARIANT_API = "dd-force-legacy-variant-api" + internal const val DD_API_KEY = "DD_API_KEY" internal const val DATADOG_API_KEY = "DATADOG_API_KEY" diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdNdkSymbolFileUploadTask.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdNdkSymbolFileUploadTask.kt deleted file mode 100644 index 93b6f3ab..00000000 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdNdkSymbolFileUploadTask.kt +++ /dev/null @@ -1,160 +0,0 @@ -package com.datadog.gradle.plugin - -import com.android.build.gradle.api.ApplicationVariant -import com.android.build.gradle.tasks.ExternalNativeBuildTask -import com.datadog.gradle.plugin.internal.ApiKey -import com.datadog.gradle.plugin.internal.Uploader -import org.gradle.api.Project -import org.gradle.api.file.ConfigurableFileCollection -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.logging.Logging -import org.gradle.api.model.ObjectFactory -import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.TaskProvider -import java.io.File -import javax.inject.Inject -import kotlin.reflect.full.memberProperties - -/** - * A Gradle task to upload NDK symbol files to Datadog servers. - */ -internal abstract class DdNdkSymbolFileUploadTask @Inject constructor( - objectFactory: ObjectFactory, - providerFactory: ProviderFactory, - repositoryDetector: RepositoryDetector -) : DdFileUploadTask(providerFactory, repositoryDetector) { - - @get:InputFiles - val searchDirectories: ConfigurableFileCollection = objectFactory.fileCollection() - - init { - description = - "Uploads NDK symbol files to Datadog servers to perform native crash symbolication." - } - - override fun getFilesList(): List { - val files = mutableListOf() - - searchDirectories - .flatMap(this::findSoFiles) - .toSet() - .forEach { file -> - val arch = file.parentFile.name - require(SUPPORTED_ARCHS.contains(arch)) - files.add( - Uploader.UploadFileInfo( - KEY_NDK_SYMBOL_FILE, - file, - encoding = ENCODING, - TYPE_NDK_SYMBOL_FILE, - file.name, - mapOf( - "arch" to arch - ) - ) - ) - } - - return files - } - - private fun findSoFiles(searchDirectory: File): Collection { - return if (searchDirectory.exists() && searchDirectory.isDirectory) { - searchDirectory.walkTopDown() - .filter { it.extension == "so" } - .toSet() - } else { - emptySet() - } - } - - companion object { - internal const val TASK_NAME = "uploadNdkSymbolFiles" - internal const val KEY_NDK_SYMBOL_FILE = "ndk_symbol_file" - internal const val TYPE_NDK_SYMBOL_FILE = "ndk_symbol_file" - internal const val ENCODING = "application/octet-stream" - internal val SUPPORTED_ARCHS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") - - internal val LOGGER = Logging.getLogger("DdSymbolFileUploadTask") - - private fun getSearchDirs( - buildTask: TaskProvider, - providerFactory: ProviderFactory - ): Provider { - return buildTask.flatMap { task -> - // var soFolder: `Provider - @Suppress("MagicNumber") - if (DdTaskUtils.isAgpAbove(8, 0, 0)) { - task.soFolder.map { it.asFile } - } else { - val soFolder = ExternalNativeBuildTask::class.memberProperties.find { - it.name == "objFolder" - }?.get(task) - when (soFolder) { - is File -> providerFactory.provider { soFolder } - is DirectoryProperty -> soFolder.map { it.asFile } - else -> providerFactory.provider { null } - } - } - } - } - - @Suppress("LongParameterList", "ReturnCount") - fun register( - project: Project, - variant: ApplicationVariant, - buildIdTask: TaskProvider, - providerFactory: ProviderFactory, - apiKey: ApiKey, - extensionConfiguration: DdExtensionConfiguration, - repositoryDetector: RepositoryDetector - ): TaskProvider? { - val nativeBuildProviders = variant.externalNativeBuildProviders - if (nativeBuildProviders.isEmpty()) { - LOGGER.info("No native build tasks found for variant ${variant.name}, skipping NDK symbol file upload.") - return null - } - - return project.tasks.register( - TASK_NAME + variant.name.capitalize(), - DdNdkSymbolFileUploadTask::class.java, - repositoryDetector - ).apply { - configure { task -> - val roots = mutableListOf() - variant.sourceSets.forEach { - roots.addAll(it.javaDirectories) - @Suppress("MagicNumber") - if (DdTaskUtils.isAgpAbove(7, 0, 0)) { - roots.addAll(it.kotlinDirectories) - } - } - task.sourceSetRoots = roots - - nativeBuildProviders.forEach { buildTask -> - val searchFiles = getSearchDirs(buildTask, providerFactory) - - task.searchDirectories.from(searchFiles) - task.dependsOn(buildTask) - } - - task.datadogCiFile = DdTaskUtils.findDatadogCiFile(project.rootDir) - task.repositoryFile = DdTaskUtils.resolveDatadogRepositoryFile(project) - task.configureWith( - apiKey, - extensionConfiguration, - variant - ) - - task.buildId = buildIdTask.flatMap { - it.buildIdFile.flatMap { - providerFactory.provider { it.asFile.readText().trim() } - } - } - } - } - } - } -} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdTaskUtils.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdTaskUtils.kt deleted file mode 100644 index bd36441f..00000000 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdTaskUtils.kt +++ /dev/null @@ -1,43 +0,0 @@ -package com.datadog.gradle.plugin - -import com.android.builder.model.Version -import org.gradle.api.Project -import java.io.File - -internal object DdTaskUtils { - private const val MAX_DATADOG_CI_FILE_LOOKUP_LEVELS = 4 - - @Suppress("StringLiteralDuplication") - fun resolveDatadogRepositoryFile(target: Project): File { - val outputsDir = File(target.buildDir, "outputs") - val reportsDir = File(outputsDir, "reports") - val datadogDir = File(reportsDir, "datadog") - return File(datadogDir, "repository.json") - } - - fun findDatadogCiFile(projectDir: File): File? { - var currentDir: File? = projectDir - var levelsUp = 0 - while (currentDir != null && levelsUp < MAX_DATADOG_CI_FILE_LOOKUP_LEVELS) { - val datadogCiFile = File(currentDir, "datadog-ci.json") - if (datadogCiFile.exists()) { - return datadogCiFile - } - currentDir = currentDir.parentFile - levelsUp++ - } - return null - } - - @Suppress("MagicNumber", "ReturnCount") - fun isAgpAbove(major: Int, minor: Int, patch: Int): Boolean { - val version = Version.ANDROID_GRADLE_PLUGIN_VERSION - val groups = version.split(".") - if (groups.size < 3) return false - val currentMajor = groups[0].toIntOrNull() - val currentMinor = groups[1].toIntOrNull() - val currentPatch = groups[2].substringBefore("-").toIntOrNull() - if (currentMajor == null || currentMinor == null || currentPatch == null) return false - return currentMajor >= major && currentMinor >= minor && currentPatch >= patch - } -} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdFileUploadTask.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/FileUploadTask.kt similarity index 82% rename from dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdFileUploadTask.kt rename to dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/FileUploadTask.kt index 6cb8cefd..5ea9e2ac 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdFileUploadTask.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/FileUploadTask.kt @@ -1,13 +1,21 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + package com.datadog.gradle.plugin -import com.android.build.gradle.api.ApplicationVariant import com.datadog.gradle.plugin.internal.ApiKey import com.datadog.gradle.plugin.internal.ApiKeySource import com.datadog.gradle.plugin.internal.DdAppIdentifier import com.datadog.gradle.plugin.internal.OkHttpUploader import com.datadog.gradle.plugin.internal.Uploader +import com.datadog.gradle.plugin.internal.variant.AppVariant import org.gradle.api.DefaultTask import org.gradle.api.logging.Logging +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.Provider import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input @@ -27,10 +35,11 @@ import javax.inject.Inject * A Gradle task to upload symbolication files to Datadog servers (NDK symbol files, * Proguard/R8 files, etc.).. */ -abstract class DdFileUploadTask @Inject constructor( - private val providerFactory: ProviderFactory, +abstract class FileUploadTask @Inject constructor( + providerFactory: ProviderFactory, @get:Internal internal val repositoryDetector: RepositoryDetector ) : DefaultTask() { + @get:Internal internal var uploader: Uploader = OkHttpUploader() @@ -41,7 +50,11 @@ abstract class DdFileUploadTask @Inject constructor( var apiKey: String = "" private val disableGzipOption: Provider = - providerFactory.gradleProperty(DdFileUploadTask.DISABLE_GZIP_GRADLE_PROPERTY) + providerFactory.gradleProperty(DISABLE_GZIP_GRADLE_PROPERTY) + + // needed for functional tests, because we don't have real API key + private val emulateNetworkCall: Provider = + providerFactory.gradleProperty(EMULATE_UPLOAD_NETWORK_CALL) /** * Source of the API key set: environment, gradle property, etc. @@ -59,20 +72,20 @@ abstract class DdFileUploadTask @Inject constructor( * The version name of the application. */ @get:Input - var versionName: String = "" + abstract val versionName: Property /** * The version code of the application. Need to be a provider, because resolution during * configuration phase may cause incompatibility with other plugins if legacy Variant API is used. */ @get:Input - var versionCode: Provider = providerFactory.provider { 0 } + abstract val versionCode: Property /** * The service name of the application (by default, it is your app's package name). */ @get:Input - var serviceName: String = "" + abstract val serviceName: Property /** * The Datadog site to upload to (one of "US1", "EU1", "US1_FED"). @@ -90,7 +103,7 @@ abstract class DdFileUploadTask @Inject constructor( * Build ID which will be used for mapping file matching. */ @get:Input - var buildId: Provider = providerFactory.provider { "" } + abstract val buildId: Property /** * datadog-ci.json file, if found or applicable for the particular task. @@ -103,7 +116,7 @@ abstract class DdFileUploadTask @Inject constructor( * The sourceSet root folders. */ @get:InputFiles - var sourceSetRoots: List = emptyList() + abstract val sourceSetRoots: ListProperty /** * The file containing the repository description. @@ -117,14 +130,11 @@ abstract class DdFileUploadTask @Inject constructor( outputs.upToDateWhen { false } } - @Internal - internal abstract fun getFilesList(): List - /** * Uploads the files retrieved from `getFilesList` to Datadog. */ @TaskAction - @Suppress("TooGenericExceptionCaught") + @Suppress("TooGenericExceptionCaught", "LongMethod") fun applyTask() { datadogCiFile?.let { applyDatadogCiConfig(it) @@ -143,8 +153,15 @@ abstract class DdFileUploadTask @Inject constructor( val mappingFiles = getFilesList() if (mappingFiles.isEmpty()) return + // it can be an overlap between java and kotlin directories and since File doesn't override + // equals for set comparison, we will remove duplicates manually + val uniqueSourceSetRoots = sourceSetRoots.get() + .map { it.absolutePath } + .distinct() + .map { File(it) } + val repositories = repositoryDetector.detectRepositories( - sourceSetRoots, + uniqueSourceSetRoots, remoteRepositoryUrl ) @@ -163,14 +180,15 @@ abstract class DdFileUploadTask @Inject constructor( if (repositories.isEmpty()) null else repositoryFile, apiKey, DdAppIdentifier( - serviceName = serviceName, - version = versionName, + serviceName = serviceName.get(), + version = versionName.get(), versionCode = versionCode.get(), variant = variantName, buildId = buildId.get() ), repositories.firstOrNull(), - !disableGzipOption.isPresent + !disableGzipOption.isPresent, + emulateNetworkCall.isPresent ) } catch (e: Exception) { caughtErrors.add(e) @@ -190,22 +208,37 @@ abstract class DdFileUploadTask @Inject constructor( } } + // region Internal + + @Internal + internal abstract fun getFilesList(): List + internal fun configureWith( apiKey: ApiKey, extensionConfiguration: DdExtensionConfiguration, - variant: ApplicationVariant + variant: AppVariant ) { this.apiKey = apiKey.value apiKeySource = apiKey.source site = extensionConfiguration.site ?: "" - versionName = variant.versionName ?: "" - versionCode = providerFactory.provider { variant.versionCode } - serviceName = extensionConfiguration.serviceName ?: variant.applicationId + versionName.set(variant.versionName) + versionCode.set(variant.versionCode) + + if (extensionConfiguration.serviceName != null) { + serviceName.set(extensionConfiguration.serviceName) + } else { + serviceName.set(variant.applicationId) + } + variantName = variant.flavorName remoteRepositoryUrl = extensionConfiguration.remoteRepositoryUrl ?: "" } + // endregion + + // region Private + private fun applySiteFromEnvironment() { val environmentSite = System.getenv(DATADOG_SITE) if (!environmentSite.isNullOrEmpty()) { @@ -298,15 +331,17 @@ abstract class DdFileUploadTask @Inject constructor( } val jsonObject = JSONObject() - jsonObject.put("version", RESPOSITORY_FILE_VERSION) + jsonObject.put("version", REPOSITORY_FILE_VERSION) jsonObject.put("data", data) repositoryFile.parentFile.mkdirs() repositoryFile.writeText(jsonObject.toString(0)) } + // endregion + internal companion object { - private const val RESPOSITORY_FILE_VERSION = 1 + private const val REPOSITORY_FILE_VERSION = 1 private const val INDENT = 4 private const val DATADOG_CI_API_KEY_PROPERTY = "apiKey" @@ -316,6 +351,7 @@ abstract class DdFileUploadTask @Inject constructor( internal val LOGGER = Logging.getLogger("DdFileUploadTask") const val DISABLE_GZIP_GRADLE_PROPERTY = "dd-disable-gzip" + const val EMULATE_UPLOAD_NETWORK_CALL = "dd-emulate-upload-call" const val API_KEY_MISSING_ERROR = "Make sure you define an API KEY to upload your mapping files to Datadog. " + "Create a DD_API_KEY or DATADOG_API_KEY environment variable, gradle" + diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/GenerateBuildIdTask.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/GenerateBuildIdTask.kt index b232ccab..715f2790 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/GenerateBuildIdTask.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/GenerateBuildIdTask.kt @@ -1,6 +1,12 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + package com.datadog.gradle.plugin -import com.android.build.gradle.api.ApplicationVariant +import com.datadog.gradle.plugin.internal.variant.AppVariant import org.gradle.api.DefaultTask import org.gradle.api.Project import org.gradle.api.file.Directory @@ -57,8 +63,8 @@ abstract class GenerateBuildIdTask : DefaultTask() { .writeText(buildId) } - companion object { - internal const val TASK_NAME = "generateBuildId" + internal companion object { + private const val TASK_NAME = "generateBuildId" /** * Name of the file containing build ID information. @@ -66,11 +72,11 @@ abstract class GenerateBuildIdTask : DefaultTask() { const val BUILD_ID_FILE_NAME = "datadog.buildId" /** - * Registers a new instance of [GenerateBuildIdTask] specific for the given [ApplicationVariant]. + * Registers a new instance of [GenerateBuildIdTask] specific for the given [AppVariant]. */ fun register( target: Project, - variant: ApplicationVariant, + variant: AppVariant, buildIdDirectory: Provider ): TaskProvider { val generateBuildIdTask = target.tasks.register( @@ -80,6 +86,7 @@ abstract class GenerateBuildIdTask : DefaultTask() { it.buildIdDirectory.set(buildIdDirectory) it.variantName.set(variant.name) } + variant.bindWith(generateBuildIdTask, buildIdDirectory) return generateBuildIdTask } diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdMappingFileUploadTask.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/MappingFileUploadTask.kt similarity index 70% rename from dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdMappingFileUploadTask.kt rename to dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/MappingFileUploadTask.kt index 62ce55d9..ed2fffa3 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/DdMappingFileUploadTask.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/MappingFileUploadTask.kt @@ -6,27 +6,31 @@ package com.datadog.gradle.plugin -import com.datadog.gradle.plugin.DdAndroidGradlePlugin.Companion.LOGGER import com.datadog.gradle.plugin.internal.Uploader +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles import java.io.File import javax.inject.Inject /** * A Gradle task to upload a Proguard/R8 mapping file to Datadog servers. */ -open class DdMappingFileUploadTask +abstract class MappingFileUploadTask @Inject constructor( providerFactory: ProviderFactory, repositoryDetector: RepositoryDetector -) : DdFileUploadTask(providerFactory, repositoryDetector) { +) : FileUploadTask(providerFactory, repositoryDetector) { /** * The path to the mapping file to upload. */ - @get:Input - var mappingFilePath: String = "" + // don't use InputFile here, because it will check if file exists before executing task, + // we want to do it manually and show our own error + @get:InputFiles + abstract val mappingFile: RegularFileProperty /** * Replacements for the source prefixes in the mapping file. @@ -40,6 +44,12 @@ open class DdMappingFileUploadTask @get:Input var mappingFileTrimIndents: Boolean = false + /** + * Application ID (usually a package prefix). + */ + @get:Input + abstract val applicationId: Property + init { group = DdAndroidGradlePlugin.DATADOG_TASK_GROUP description = "Uploads the Proguard/R8 mapping file to Datadog" @@ -48,7 +58,10 @@ open class DdMappingFileUploadTask } override fun getFilesList(): List { - var mappingFile = File(mappingFilePath) + check(mappingFile.isPresent) { + "No mapping file value in the input property." + } + var mappingFile = mappingFile.get().asFile if (!validateMappingFile(mappingFile)) return emptyList() mappingFile = shrinkMappingFile(mappingFile) @@ -67,13 +80,13 @@ open class DdMappingFileUploadTask @Suppress("CheckInternal") private fun validateMappingFile(mappingFile: File): Boolean { if (!mappingFile.exists()) { - println("There's no mapping file $mappingFilePath, nothing to upload") + println("There's no mapping file ${mappingFile.absolutePath}, nothing to upload") return false } - check(mappingFile.isFile) { "Expected $mappingFilePath to be a file" } + check(mappingFile.isFile) { "Expected ${mappingFile.absolutePath} to be a file" } - check(mappingFile.canRead()) { "Cannot read file $mappingFilePath" } + check(mappingFile.canRead()) { "Cannot read file ${mappingFile.absolutePath}" } return true } @@ -92,12 +105,28 @@ open class DdMappingFileUploadTask if (shrinkedFile.exists()) { shrinkedFile.delete() } - // sort is needed to have predictable replacement in the following case: - // imagine there are 2 keys - "androidx.work" and "androidx.work.Job", and the latter - // occurs much more often than the rest under "androidx.work.*". So for the more efficient - // compression we need first to process the replacement of "androidx.work.Job" and only - // after that any possible prefix (which has a smaller length). - val replacements = mappingFilePackagesAliases.entries + + val replacements = mappingFilePackagesAliases + .filter { + // not necessarily applicationId == package attribute from the Manifest, but it is + // best and cheapest effort to avoid wrong renaming (otherwise we may loose Git + // integration feature). + if (applicationId.get().startsWith(it.key)) { + DdAndroidGradlePlugin.LOGGER.warn( + "Renaming of package prefix=${it.key} will be skipped, because" + + " it belongs to the application package." + ) + false + } else { + true + } + } + .entries + // sort is needed to have predictable replacement in the following case: + // imagine there are 2 keys - "androidx.work" and "androidx.work.Job", and the latter + // occurs much more often than the rest under "androidx.work.*". So for the more efficient + // compression we need first to process the replacement of "androidx.work.Job" and only + // after that any possible prefix (which has a smaller length). .sortedByDescending { it.key.length } .map { Regex("(?<=^|\\W)${it.key}(?=\\W)") to it.value diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/NdkSymbolFileUploadTask.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/NdkSymbolFileUploadTask.kt new file mode 100644 index 00000000..30efd24d --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/NdkSymbolFileUploadTask.kt @@ -0,0 +1,127 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin + +import com.datadog.gradle.plugin.internal.ApiKey +import com.datadog.gradle.plugin.internal.Uploader +import com.datadog.gradle.plugin.internal.lazyBuildIdProvider +import com.datadog.gradle.plugin.internal.variant.AppVariant +import org.gradle.api.Project +import org.gradle.api.file.ConfigurableFileCollection +import org.gradle.api.model.ObjectFactory +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.TaskProvider +import java.io.File +import javax.inject.Inject + +/** + * A Gradle task to upload NDK symbol files to Datadog servers. + */ +internal abstract class NdkSymbolFileUploadTask @Inject constructor( + objectFactory: ObjectFactory, + providerFactory: ProviderFactory, + repositoryDetector: RepositoryDetector +) : FileUploadTask(providerFactory, repositoryDetector) { + + @get:InputFiles + val searchDirectories: ConfigurableFileCollection = objectFactory.fileCollection() + + init { + description = + "Uploads NDK symbol files to Datadog servers to perform native crash symbolication." + } + + override fun getFilesList(): List { + val files = mutableListOf() + + searchDirectories + .flatMap(this::findSoFiles) + .toSet() + .forEach { file -> + val arch = file.parentFile.name + val archMapping = SUPPORTED_ARCHS.firstOrNull { it.arch == arch } + require(archMapping != null) + files.add( + Uploader.UploadFileInfo( + KEY_NDK_SYMBOL_FILE, + file, + encoding = ENCODING, + TYPE_NDK_SYMBOL_FILE, + file.name, + mapOf( + "arch" to archMapping.uploadArch + ) + ) + ) + } + + return files + } + + private fun findSoFiles(searchDirectory: File): Collection { + return if (searchDirectory.exists() && searchDirectory.isDirectory) { + searchDirectory.walkTopDown() + .filter { it.extension == "so" } + .toSet() + } else { + emptySet() + } + } + + // Map of Android architecture names to the architecture names recognized by the symbolication service + data class SupportedArchitectureMapping( + val arch: String, + val uploadArch: String + ) + + companion object { + internal const val TASK_NAME = "uploadNdkSymbolFiles" + internal const val KEY_NDK_SYMBOL_FILE = "ndk_symbol_file" + internal const val TYPE_NDK_SYMBOL_FILE = "ndk_symbol_file" + internal const val ENCODING = "application/octet-stream" + internal val SUPPORTED_ARCHS = setOf( + SupportedArchitectureMapping("armeabi-v7a", "arm"), + SupportedArchitectureMapping("arm64-v8a", "arm64"), + SupportedArchitectureMapping("x86", "x86"), + SupportedArchitectureMapping("x86_64", "x64") + ) + + @Suppress("LongParameterList", "ReturnCount") + fun register( + project: Project, + variant: AppVariant, + buildIdTask: TaskProvider, + providerFactory: ProviderFactory, + apiKey: ApiKey, + extensionConfiguration: DdExtensionConfiguration, + repositoryDetector: RepositoryDetector + ): TaskProvider { + return project.tasks.register( + TASK_NAME + variant.name.capitalize(), + NdkSymbolFileUploadTask::class.java, + repositoryDetector + ).apply { + configure { task -> + task.sourceSetRoots.set(variant.collectJavaAndKotlinSourceDirectories()) + + variant.bindWith(task) + + task.datadogCiFile = TaskUtils.findDatadogCiFile(project.rootDir) + task.repositoryFile = TaskUtils.resolveDatadogRepositoryFile(project) + task.configureWith( + apiKey, + extensionConfiguration, + variant + ) + + task.buildId.set(buildIdTask.lazyBuildIdProvider(providerFactory)) + } + } + } + } +} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/TaskUtils.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/TaskUtils.kt new file mode 100644 index 00000000..92ac6644 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/TaskUtils.kt @@ -0,0 +1,78 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin + +import com.android.builder.model.Version +import org.gradle.api.Project +import java.io.File + +internal object TaskUtils { + + private const val MAX_DATADOG_CI_FILE_LOOKUP_LEVELS = 4 + + @Suppress("StringLiteralDuplication") + fun resolveDatadogRepositoryFile(target: Project): File { + val outputsDir = File(target.buildDir, "outputs") + val reportsDir = File(outputsDir, "reports") + val datadogDir = File(reportsDir, "datadog") + return File(datadogDir, "repository.json") + } + + fun findDatadogCiFile(projectDir: File): File? { + var currentDir: File? = projectDir + var levelsUp = 0 + while (currentDir != null && levelsUp < MAX_DATADOG_CI_FILE_LOOKUP_LEVELS) { + val datadogCiFile = File(currentDir, "datadog-ci.json") + if (datadogCiFile.exists()) { + return datadogCiFile + } + currentDir = currentDir.parentFile + levelsUp++ + } + return null + } + + fun isAgpAbove(major: Int, minor: Int, patch: Int): Boolean { + return isVersionAbove(Version.ANDROID_GRADLE_PLUGIN_VERSION, major, minor, patch) + } + + // Gradle version may not have patch version + fun isGradleAbove(project: Project, major: Int, minor: Int, patch: Int = 0): Boolean { + return isVersionAbove(project.gradle.gradleVersion, major, minor, patch) + } + + @Suppress("MagicNumber", "ReturnCount") + private fun isVersionAbove(refVersion: String, major: Int, minor: Int, patch: Int): Boolean { + val groups = refVersion.split(".") + // should be at least major and minor versions + if (groups.size < 2) return false + val currentMajor = groups[0].toIntOrNull() ?: 0 + val currentMinor = groups[1].toIntOrNull() ?: 0 + val currentPatch = groups.getOrNull(2)?.substringBefore("-")?.toIntOrNull() ?: 0 + return currentMajor >= major && currentMinor >= minor && currentPatch >= patch + } +} + +@Deprecated( + message = "Renamed to MappingFileUploadTask", + replaceWith = ReplaceWith( + expression = "MappingFileUploadTask", + imports = arrayOf("com.datadog.gradle.plugin.MappingFileUploadTask") + ) +) +@Suppress("unused") +typealias DdMappingFileUploadTask = MappingFileUploadTask + +@Deprecated( + message = "Renamed to CheckSdkDepsTask", + replaceWith = ReplaceWith( + expression = "CheckSdkDepsTask", + imports = arrayOf("com.datadog.gradle.plugin.CheckSdkDepsTask") + ) +) +@Suppress("unused") +typealias DdCheckSdkDepsTask = CheckSdkDepsTask diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/CurrentAgpVersion.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/CurrentAgpVersion.kt new file mode 100644 index 00000000..8d99fa77 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/CurrentAgpVersion.kt @@ -0,0 +1,26 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal + +import com.datadog.gradle.plugin.TaskUtils + +@Suppress("MagicNumber") +internal object CurrentAgpVersion { + + // can work probably even with lower versions, but legacy Variant API is working fine there as well + val CAN_ENABLE_NEW_VARIANT_API: Boolean + get() = TaskUtils.isAgpAbove(major = 8, minor = 4, patch = 0) + + val SUPPORTS_KOTLIN_DIRECTORIES_SOURCE_PROVIDER: Boolean + get() = TaskUtils.isAgpAbove(major = 7, minor = 0, patch = 0) + + val EXTERNAL_NATIVE_BUILD_SOFOLDER_IS_PUBLIC: Boolean + get() = TaskUtils.isAgpAbove(major = 8, minor = 0, patch = 0) + + val CAN_QUERY_MAPPING_FILE_PROVIDER: Boolean + get() = TaskUtils.isAgpAbove(major = 7, minor = 0, patch = 0) +} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploader.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploader.kt index 8f722eeb..56c90c28 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploader.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploader.kt @@ -13,6 +13,7 @@ import okhttp3.MediaType import okhttp3.MediaType.Companion.toMediaTypeOrNull import okhttp3.MultipartBody import okhttp3.OkHttpClient +import okhttp3.Protocol import okhttp3.Request import okhttp3.RequestBody import okhttp3.RequestBody.Companion.asRequestBody @@ -52,7 +53,8 @@ internal class OkHttpUploader : Uploader { apiKey: String, identifier: DdAppIdentifier, repositoryInfo: RepositoryInfo?, - useGzip: Boolean + useGzip: Boolean, + emulateNetworkCall: Boolean ) { LOGGER.info("Uploading file ${fileInfo.fileName} with tags $identifier (site=${site.domain}):") if (fileInfo.extraAttributes.isNotEmpty()) { @@ -81,7 +83,16 @@ internal class OkHttpUploader : Uploader { val call = client.newCall(request) val response = try { - call.execute() + if (emulateNetworkCall) { + Response.Builder() + .request(request) + .protocol(Protocol.HTTP_2) + .message("fake-response") + .code(HttpURLConnection.HTTP_ACCEPTED) + .build() + } else { + call.execute() + } } catch (e: Throwable) { LOGGER.error("Error uploading the mapping file for $identifier", e) null @@ -272,7 +283,7 @@ internal class OkHttpUploader : Uploader { internal const val KEY_EVENT = "event" internal const val KEY_REPOSITORY = "repository" - internal val NETWORK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(60) + internal val NETWORK_TIMEOUT_MS = TimeUnit.SECONDS.toMillis(120) internal val MEDIA_TYPE_JSON = "application/json".toMediaTypeOrNull() internal const val MAX_MAP_SIZE_EXCEEDED_ERROR = diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/TaskExt.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/TaskExt.kt new file mode 100644 index 00000000..307c3034 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/TaskExt.kt @@ -0,0 +1,54 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal + +import com.android.build.gradle.tasks.ExternalNativeBuildTask +import com.datadog.gradle.plugin.GenerateBuildIdTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Provider +import org.gradle.api.provider.ProviderFactory +import org.gradle.api.tasks.TaskProvider +import java.io.File +import kotlin.reflect.full.memberProperties + +internal fun TaskProvider.getSearchObjDirs(providerFactory: ProviderFactory): Provider { + return flatMap { task -> task.getSearchObjDirs(providerFactory) } +} + +internal fun ExternalNativeBuildTask.getSearchObjDirs(providerFactory: ProviderFactory): Provider { + return if (CurrentAgpVersion.EXTERNAL_NATIVE_BUILD_SOFOLDER_IS_PUBLIC) { + soFolder.map { it.asFile } + } else { + val soFolder = ExternalNativeBuildTask::class.memberProperties.find { + it.name == "objFolder" + }?.get(this) + when (soFolder) { + is File -> providerFactory.provider { soFolder } + is DirectoryProperty -> soFolder.map { it.asFile } + else -> providerFactory.provider { null } + } + } +} + +internal fun TaskProvider.lazyBuildIdProvider(providerFactory: ProviderFactory): Provider { + // upload task shouldn't depend on the build ID generation task, but only read its property, + // because upload task may be triggered after assemble task and we don't want to re-generate + // build ID, because it will be different then from the one which is already embedded in + // the application package + return flatMap { + it.buildIdFile.flatMap { + providerFactory.provider { + val file = it.asFile + if (file.exists()) { + file.readText().trim() + } else { + "" + } + } + } + } +} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/Uploader.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/Uploader.kt index 0e601c05..c9dc9473 100644 --- a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/Uploader.kt +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/Uploader.kt @@ -56,6 +56,7 @@ internal interface Uploader { apiKey: String, identifier: DdAppIdentifier, repositoryInfo: RepositoryInfo?, - useGzip: Boolean + useGzip: Boolean = true, + emulateNetworkCall: Boolean = false ) } diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/AppVariant.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/AppVariant.kt new file mode 100644 index 00000000..249d3be6 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/AppVariant.kt @@ -0,0 +1,68 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal.variant + +import com.android.build.gradle.AppExtension +import com.datadog.gradle.plugin.GenerateBuildIdTask +import com.datadog.gradle.plugin.MappingFileUploadTask +import com.datadog.gradle.plugin.NdkSymbolFileUploadTask +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider +import java.io.File +import com.android.build.api.variant.ApplicationVariant as NewApplicationVariant +import com.android.build.gradle.api.ApplicationVariant as LegacyApplicationVariant + +internal interface AppVariant { + + val name: String + + val applicationId: Provider + + val flavorName: String + + val versionCode: Provider + + val versionName: Provider + + val compileConfiguration: Configuration + + val isNativeBuildEnabled: Boolean + + val isMinifyEnabled: Boolean + + val buildTypeName: String + + val flavors: List + + val mappingFile: Provider + + fun collectJavaAndKotlinSourceDirectories(): Provider> + + fun bindWith(ndkUploadTask: NdkSymbolFileUploadTask) + + fun bindWith(mappingFileUploadTask: MappingFileUploadTask) + + // new variant API doesn't allow to run addGeneratedSourceDirectory from inside Task#configure, thus this + fun bindWith(generateBuildIdTask: TaskProvider, buildIdDirectory: Provider) + + companion object { + fun create( + variant: NewApplicationVariant, + target: Project + ): AppVariant = NewApiAppVariant(variant, target) + + fun create( + variant: LegacyApplicationVariant, + appExtension: AppExtension, + target: Project + ): AppVariant = LegacyApiAppVariant(variant, appExtension, target) + } +} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/LegacyApiAppVariant.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/LegacyApiAppVariant.kt new file mode 100644 index 00000000..e0d4a962 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/LegacyApiAppVariant.kt @@ -0,0 +1,135 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal.variant + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.api.ApplicationVariant +import com.datadog.gradle.plugin.DdAndroidGradlePlugin +import com.datadog.gradle.plugin.GenerateBuildIdTask +import com.datadog.gradle.plugin.MappingFileUploadTask +import com.datadog.gradle.plugin.NdkSymbolFileUploadTask +import com.datadog.gradle.plugin.internal.CurrentAgpVersion +import com.datadog.gradle.plugin.internal.getSearchObjDirs +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths + +internal class LegacyApiAppVariant( + private val variant: ApplicationVariant, + private val appExtension: AppExtension, + private val target: Project +) : AppVariant { + + private val providerFactory = target.providers + private val projectLayout = target.layout + + override val name: String + get() = variant.name + override val applicationId: Provider + get() = providerFactory.provider { variant.applicationId } + override val flavorName: String + get() = variant.flavorName + override val versionCode: Provider + get() = providerFactory.provider { variant.versionCode } + override val versionName: Provider + get() = providerFactory.provider { variant.versionName.orEmpty() } + override val compileConfiguration: Configuration + get() = variant.compileConfiguration + override val isNativeBuildEnabled: Boolean + get() = variant.externalNativeBuildProviders.isNotEmpty() + override val isMinifyEnabled: Boolean + get() = variant.buildType.isMinifyEnabled + override val buildTypeName: String + get() = variant.buildType.name + override val flavors: List + get() = variant.productFlavors.map { it.name } + override val mappingFile: Provider + get() = if (CurrentAgpVersion.CAN_QUERY_MAPPING_FILE_PROVIDER) { + variant.mappingFileProvider + .flatMap { + providerFactory.provider { + try { + projectLayout.projectDirectory.file(it.singleFile.absolutePath) + } catch (e: IllegalStateException) { + DdAndroidGradlePlugin.LOGGER.info( + "Mapping FileCollection is empty or contains multiple files", + e + ) + null + } + }.orElse(legacyMappingFileProvider) + } + } else { + legacyMappingFileProvider + } + + private val legacyMappingFileProvider: Provider + get() = projectLayout.buildDirectory.file(legacyMappingFilePath.toString()) + + private val legacyMappingFilePath: Path + get() = Paths.get("outputs", "mapping", variant.name, "mapping.txt") + + override fun collectJavaAndKotlinSourceDirectories(): Provider> { + val roots = mutableListOf() + variant.sourceSets.forEach { + roots.addAll(it.javaDirectories) + if (CurrentAgpVersion.SUPPORTS_KOTLIN_DIRECTORIES_SOURCE_PROVIDER) { + roots.addAll(it.kotlinDirectories) + } + } + return providerFactory.provider { roots } + } + + override fun bindWith(ndkUploadTask: NdkSymbolFileUploadTask) { + val nativeBuildProviders = variant.externalNativeBuildProviders + nativeBuildProviders.forEach { buildTask -> + val searchFiles = buildTask.getSearchObjDirs(providerFactory) + + ndkUploadTask.searchDirectories.from(searchFiles) + ndkUploadTask.dependsOn(buildTask) + } + } + + override fun bindWith(mappingFileUploadTask: MappingFileUploadTask) { + val minifyTask = target.tasks.findByName("minify${variant.name.capitalize()}WithR8") ?: return + mappingFileUploadTask.dependsOn(minifyTask) + } + + override fun bindWith( + generateBuildIdTask: TaskProvider, + buildIdDirectory: Provider + ) { + // we could generate buildIdDirectory inside GenerateBuildIdTask and read it here as + // property using flatMap, but when Gradle sync is done inside Android Studio there is an error + // Querying the mapped value of provider (java.util.Set) before task ... has completed is + // not supported, which doesn't happen when Android Studio is not used (pure Gradle build) + // so applying such workaround + appExtension.sourceSets.getByName(variant.name).assets.srcDir(buildIdDirectory) + + val variantName = variant.name.capitalize() + listOf( + "package${variantName}Bundle", + "build${variantName}PreBundle", + "lintVitalAnalyze$variantName", + "lintVitalReport$variantName", + "generate${variantName}LintVitalReportModel" + ).forEach { + target.tasks.findByName(it)?.dependsOn(generateBuildIdTask) + } + + // don't merge these 2 into list to call forEach, because common superclass for them + // is different between AGP versions, which may cause ClassCastException + variant.mergeAssetsProvider.configure { it.dependsOn(generateBuildIdTask) } + variant.packageApplicationProvider.configure { it.dependsOn(generateBuildIdTask) } + } +} diff --git a/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/NewApiAppVariant.kt b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/NewApiAppVariant.kt new file mode 100644 index 00000000..fbe0fc8d --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/main/kotlin/com/datadog/gradle/plugin/internal/variant/NewApiAppVariant.kt @@ -0,0 +1,105 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal.variant + +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationVariant +import com.android.build.api.variant.VariantOutput +import com.android.build.gradle.tasks.ExternalNativeBuildTask +import com.datadog.gradle.plugin.GenerateBuildIdTask +import com.datadog.gradle.plugin.MappingFileUploadTask +import com.datadog.gradle.plugin.NdkSymbolFileUploadTask +import com.datadog.gradle.plugin.internal.getSearchObjDirs +import org.gradle.api.Project +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile +import org.gradle.api.provider.Provider +import org.gradle.api.tasks.TaskProvider +import java.io.File + +internal class NewApiAppVariant( + private val variant: ApplicationVariant, + private val target: Project +) : AppVariant { + + private val providerFactory = target.providers + + override val name: String + get() = variant.name + override val applicationId: Provider + get() = variant.applicationId + override val flavorName: String + get() = variant.flavorName.orEmpty() + override val versionCode: Provider + get() = providerFactory.provider { variant.mainOutput?.versionCode?.orNull ?: 1 } + override val versionName: Provider + get() = providerFactory.provider { variant.mainOutput?.versionName?.orNull.orEmpty() } + override val compileConfiguration: Configuration + get() = variant.compileConfiguration + override val isNativeBuildEnabled: Boolean + get() = variant.externalNativeBuild != null + + @Suppress("UnstableApiUsage") + override val isMinifyEnabled: Boolean + get() = variant.isMinifyEnabled + override val buildTypeName: String + get() = variant.buildType.orEmpty() + override val flavors: List + // dimension to flavor name + get() = variant.productFlavors.map { it.second } + override val mappingFile: Provider + get() = variant.artifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE) + + override fun collectJavaAndKotlinSourceDirectories(): Provider> { + val allJava = variant.sources.java?.all + + @Suppress("UnstableApiUsage") + val allKotlin = variant.sources.kotlin?.all + return if (allJava != null) { + if (allKotlin != null) { + allJava.zip(allKotlin) { java, kotlin -> java + kotlin }.asFileCollectionProvider() + } else { + allJava.asFileCollectionProvider() + } + } else { + allKotlin?.asFileCollectionProvider() ?: providerFactory.provider { emptyList() } + } + } + + override fun bindWith(ndkUploadTask: NdkSymbolFileUploadTask) { + target.tasks.withType(ExternalNativeBuildTask::class.java).forEach { + val searchFiles = it.getSearchObjDirs(providerFactory) + ndkUploadTask.searchDirectories.from(searchFiles) + ndkUploadTask.dependsOn(it) + } + } + + override fun bindWith(mappingFileUploadTask: MappingFileUploadTask) { + // nothing is needed, dependency on minification task is created by mapping file provider + } + + override fun bindWith( + generateBuildIdTask: TaskProvider, + buildIdDirectory: Provider + ) { + variant.sources.assets?.addGeneratedSourceDirectory(generateBuildIdTask) { + it.buildIdDirectory + } + } + + // region Private + + private fun Provider>.asFileCollectionProvider() = + map { collection -> collection.map { it.asFile } } + + // may not be precise, but we need this info only for metadata anyway + private val ApplicationVariant.mainOutput: VariantOutput? + get() = outputs.firstOrNull { it.enabled.get() && it.versionCode.orNull != null } + + // endregion +} diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdCheckSdkDepsTaskTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/CheckSdkDepsTaskTest.kt similarity index 95% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdCheckSdkDepsTaskTest.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/CheckSdkDepsTaskTest.kt index fd9e31a1..117dd631 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdCheckSdkDepsTaskTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/CheckSdkDepsTaskTest.kt @@ -7,6 +7,7 @@ package com.datadog.gradle.plugin import com.datadog.gradle.plugin.internal.MissingSdkException +import com.datadog.gradle.plugin.utils.forge.Configurator import com.datadog.gradle.plugin.utils.setStaticValue import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryException @@ -51,8 +52,8 @@ import java.io.UncheckedIOException ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class DdCheckSdkDepsTaskTest { - lateinit var testedTask: DdCheckSdkDepsTask +internal class CheckSdkDepsTaskTest { + lateinit var testedTask: CheckSdkDepsTask lateinit var fakeSdkCheckLevel: SdkCheckLevel @@ -80,8 +81,8 @@ internal class DdCheckSdkDepsTaskTest { @BeforeEach fun `set up`(forge: Forge) { - originalLogger = DdCheckSdkDepsTask.LOGGER - DdCheckSdkDepsTask::class.java.setStaticValue("LOGGER", mockLogger) + originalLogger = CheckSdkDepsTask.LOGGER + CheckSdkDepsTask::class.java.setStaticValue("LOGGER", mockLogger) whenever(mockConfiguration.name).thenReturn(fakeConfigurationName) whenever(mockConfiguration.resolvedConfiguration).thenReturn(mockResolvedConfiguration) @@ -94,8 +95,8 @@ internal class DdCheckSdkDepsTaskTest { fakeProject.configurations.add(mockConfiguration) testedTask = fakeProject.tasks.create( - "DdCheckDepsTask", - DdCheckSdkDepsTask::class.java + "CheckDepsTask", + CheckSdkDepsTask::class.java ) { it.configurationName.set(fakeConfigurationName) it.sdkCheckLevel.set(fakeSdkCheckLevel) @@ -105,7 +106,7 @@ internal class DdCheckSdkDepsTaskTest { @AfterEach fun `tear down`() { - DdCheckSdkDepsTask::class.java.setStaticValue("LOGGER", originalLogger) + CheckSdkDepsTask::class.java.setStaticValue("LOGGER", originalLogger) } // region taskAction @@ -120,7 +121,7 @@ internal class DdCheckSdkDepsTaskTest { // THEN verify(mockLogger).info( - DdCheckSdkDepsTask.CANNOT_FIND_CONFIGURATION_MESSAGE + CheckSdkDepsTask.CANNOT_FIND_CONFIGURATION_MESSAGE .format(fakeConfigurationName, fakeVariantName) ) } @@ -206,7 +207,7 @@ internal class DdCheckSdkDepsTaskTest { testedTask.applyTask() // THEN - verify(mockLogger).warn(DdCheckSdkDepsTask.MISSING_DD_SDK_MESSAGE.format(fakeVariantName)) + verify(mockLogger).warn(CheckSdkDepsTask.MISSING_DD_SDK_MESSAGE.format(fakeVariantName)) assertThat(testedTask.isLastRunSuccessful).isFalse() } diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginFunctionalTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginFunctionalTest.kt index 0f429ed5..41a8f525 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginFunctionalTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginFunctionalTest.kt @@ -7,6 +7,7 @@ package com.datadog.gradle.plugin import com.datadog.gradle.plugin.utils.assertj.BuildResultAssert.Companion.assertThat +import com.datadog.gradle.plugin.utils.forge.Configurator import com.datadog.gradle.plugin.utils.headHash import com.datadog.gradle.plugin.utils.initializeGit import fr.xgouchet.elmyr.Forge @@ -37,6 +38,7 @@ import kotlin.io.path.Path ) @ForgeConfiguration(value = Configurator::class) internal class DdAndroidGradlePluginFunctionalTest { + @TempDir lateinit var testProjectDir: File private lateinit var appRootDir: File @@ -109,19 +111,7 @@ internal class DdAndroidGradlePluginFunctionalTest { stubFile(sampleApplicationClassFile, APPLICATION_CLASS_CONTENT) stubFile(javaPlaceholderClassFile, JAVA_CLASS_CONTENT) stubFile(appManifestFile, APP_MANIFEST_FILE_CONTENT) - stubFile( - gradlePropertiesFile, - GRADLE_PROPERTIES_FILE_CONTENT.format( - Locale.US, - buildVersionConfig.agpVersion, - buildVersionConfig.buildToolsVersion, - buildVersionConfig.targetSdkVersion, - buildVersionConfig.kotlinVersion, - PluginUnderTestMetadataReading.readImplementationClasspath() - .joinToString(",") { it.absolutePath }, - buildVersionConfig.jvmTarget - ) - ) + stubGradlePropertiesFile(buildVersionConfig) stubFile(libModulePlaceholderFile, LIB_MODULE_PLACEHOLDER_CLASS_CONTENT) stubFile(libModuleManifestFile, LIB_MODULE_MANIFEST_FILE_CONTENT) stubGradleBuildFromResourceFile( @@ -155,6 +145,21 @@ internal class DdAndroidGradlePluginFunctionalTest { assertThat(result).hasSuccessfulTaskOutcome(":samples:app:assembleRelease") } + @Test + fun `M success W assembleRelease { new Variant API is used in buildscript }`() { + // Given + stubGradleBuildFromResourceFile( + "build_with_android_components.gradle", + appBuildGradleFile + ) + // When + val result = gradleRunner { withArguments("--stacktrace", ":samples:app:assembleRelease") } + .build() + + // Then + assertThat(result).hasSuccessfulTaskOutcome(":samples:app:assembleRelease") + } + @Test fun `M success W assembleRelease { project with library module }`() { // Given @@ -260,25 +265,18 @@ internal class DdAndroidGradlePluginFunctionalTest { assertThat(result).hasSuccessfulTaskOutcome(":samples:app:assembleDebug") } - @Disabled( - "This test is ignored for now because of these tasks: " + - "collect[Flavour1][Flavour2]ReleaseDependencies. This is caused under the hood" + - "by this task: PerModuleReportDependenciesTask which accesses the project object" + - "inside the action method. " + - "There is already an opened issue here: https://github.com/gradle/gradle/issues/12871" - ) @Test fun `M success W assembleRelease { configuration cache, checkProjectDependencies enabled }`() { - // TODO: https://datadoghq.atlassian.net/browse/RUMM-1894 - // Given + // depends on https://github.com/gradle/gradle/issues/12871, which was released only with Gradle 7.5 + stubGradlePropertiesFile(LATEST_VERSIONS_TEST_CONFIGURATION) stubGradleBuildFromResourceFile( "build_with_datadog_dep.gradle", appBuildGradleFile ) // When - val result = gradleRunner { + val result = gradleRunner(gradleVersion = LATEST_VERSIONS_TEST_CONFIGURATION.gradleVersion) { withArguments( "--configuration-cache", "--stacktrace", @@ -340,25 +338,18 @@ internal class DdAndroidGradlePluginFunctionalTest { assertThat(result).hasSuccessfulTaskOutcome(":samples:app:assembleDebug") } - @Disabled( - "This test is ignored for now because of these tasks: " + - "collect[Flavour1][Flavour2]ReleaseDependencies. This is caused under the hood" + - "by this task: PerModuleReportDependenciesTask which accesses the project object" + - "inside the action method. " + - "There is already an opened issue here: https://github.com/gradle/gradle/issues/12871" - ) @Test fun `M success W assembleRelease { configuration cache, checkDependencies disabled }`() { - // TODO: https://datadoghq.atlassian.net/browse/RUMM-1894 - // Given + // depends on https://github.com/gradle/gradle/issues/12871, which was released only with Gradle 7.5 + stubGradlePropertiesFile(LATEST_VERSIONS_TEST_CONFIGURATION) stubGradleBuildFromResourceFile( "build_with_check_deps_disabled.gradle", appBuildGradleFile ) // When - val result = gradleRunner { + val result = gradleRunner(gradleVersion = LATEST_VERSIONS_TEST_CONFIGURATION.gradleVersion) { withArguments( "--configuration-cache", "--stacktrace", @@ -602,10 +593,11 @@ internal class DdAndroidGradlePluginFunctionalTest { taskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then assertThat(result).containsInOutput("Creating request with GZIP encoding.") @@ -663,10 +655,11 @@ internal class DdAndroidGradlePluginFunctionalTest { "--info", "--stacktrace", "-PDD_API_KEY=fakekey", - "-Pdd-disable-gzip" + "-Pdd-disable-gzip", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then assertThat(result).containsInOutput("Creating request without GZIP encoding.") @@ -727,8 +720,8 @@ internal class DdAndroidGradlePluginFunctionalTest { gradleRunner { withArguments("--info", ":samples:app:assembleRelease") } .build() - val result = gradleRunner { withArguments(taskName, "--info") } - .buildAndFail() + val result = gradleRunner { withArguments(taskName, "--info", "-Pdd-emulate-upload-call") } + .build() // Then val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) @@ -783,10 +776,11 @@ internal class DdAndroidGradlePluginFunctionalTest { taskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) @@ -838,10 +832,11 @@ internal class DdAndroidGradlePluginFunctionalTest { taskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) @@ -875,7 +870,7 @@ internal class DdAndroidGradlePluginFunctionalTest { "outputs", "mapping", "${variant}Release", - DdMappingFileUploadTask.MAPPING_OPTIMIZED_FILE_NAME + MappingFileUploadTask.MAPPING_OPTIMIZED_FILE_NAME ).toFile() assertThat(result).containsInOutput( "Size of optimized file is ${optimizedFile.length()} bytes" @@ -902,10 +897,11 @@ internal class DdAndroidGradlePluginFunctionalTest { taskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then assertThat(result).containsInOutput( @@ -923,6 +919,73 @@ internal class DdAndroidGradlePluginFunctionalTest { ) } + @Test + fun `M be compatible with configuration cache W assemble finalized by upload`(forge: Forge) { + // Given + // this test is using Task.notCompatibleWithConfigurationCache, which appeared only in Gradle 7.5, + // and moreover configuration cache support in Gradle is still ongoing work + stubGradlePropertiesFile(LATEST_VERSIONS_TEST_CONFIGURATION) + stubGradleBuildFromResourceFile( + "build_with_datadog_dep.gradle", + appBuildGradleFile + ) + val color = forge.anElementFrom(colors) + val version = forge.anElementFrom(versions) + val variantVersionName = version.lowercase() + val variant = "${version.lowercase()}$color" + + appBuildGradleFile.appendText( + """ + tasks.configureEach { + if (name == "minify${variant.capitalize()}ReleaseWithR8") { + finalizedBy(tasks.getByName("uploadMapping${variant.capitalize()}Release")) + } + } + """.trimIndent() + ) + + val result = gradleRunner(gradleVersion = LATEST_VERSIONS_TEST_CONFIGURATION.gradleVersion) { + withArguments( + "--info", + ":samples:app:assemble${variant.capitalize()}Release", + "--stacktrace", + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call", + "--configuration-cache" + ) + } + .build() + + // Then + assertThat(result).containsInOutput("Creating request with GZIP encoding.") + + val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) + val buildIdInApk = testProjectDir.findBuildIdInApk(variant) + assertThat(buildIdInApk).isEqualTo(buildIdInOriginFile) + + assertThat(result).containsInOutput( + "Uploading file jvm_mapping with tags " + + "`service:com.example.variants.$variantVersionName`, " + + "`version:1.0-$variantVersionName`, " + + "`version_code:1`, " + + "`variant:$variant`, " + + "`build_id:$buildIdInOriginFile` (site=datadoghq.com):" + ) + assertThat(result).containsInOutput( + """ + Detected repository: + { + "files": [ + "src/main/java/Placeholder.java", + "src/main/kotlin/SampleApplication.kt" + ], + "repository_url": "$fakeRemoteUrl", + "hash": "${headHash(appRootDir)}" + } + """.trimIndent() + ) + } + @Test fun `M not contain any uploadTasks W minifyNotEnabled`() { // Given @@ -985,10 +1048,11 @@ internal class DdAndroidGradlePluginFunctionalTest { taskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) @@ -1006,7 +1070,7 @@ internal class DdAndroidGradlePluginFunctionalTest { "`build_id:$buildIdInOriginFile` (site=datadoghq.com):" ) - for (arch in SUPPORTED_ABIS) { + for (arch in SUPPORTED_ABIS.values) { assertThat(result).containsInOutput("extra attributes: {arch=$arch}") } } @@ -1033,10 +1097,11 @@ internal class DdAndroidGradlePluginFunctionalTest { taskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) @@ -1054,7 +1119,7 @@ internal class DdAndroidGradlePluginFunctionalTest { "`build_id:$buildIdInOriginFile` (site=datadoghq.com):" ) - for (arch in SUPPORTED_ABIS) { + for (arch in SUPPORTED_ABIS.values) { assertThat(result).containsInOutput("extra attributes: {arch=$arch}") } } @@ -1086,20 +1151,22 @@ internal class DdAndroidGradlePluginFunctionalTest { ndkSymbolUploadTaskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() val mappingResult = gradleRunner { withArguments( mappingUploadTaskName, "--info", "--stacktrace", - "-PDD_API_KEY=fakekey" + "-PDD_API_KEY=fakekey", + "-Pdd-emulate-upload-call" ) } - .buildAndFail() + .build() // Then val buildIdInOriginFile = testProjectDir.findBuildIdInOriginFile(variant) @@ -1119,7 +1186,7 @@ internal class DdAndroidGradlePluginFunctionalTest { "`build_id:$buildIdInOriginFile` (site=datadoghq.com):" ) - for (arch in SUPPORTED_ABIS) { + for (arch in SUPPORTED_ABIS.values) { assertThat(nativeResult).containsInOutput("extra attributes: {arch=$arch}") } @@ -1137,8 +1204,10 @@ internal class DdAndroidGradlePluginFunctionalTest { // region Internal - private fun resolveMappingUploadTask(variantName: String) = "uploadMapping${variantName}Release" - private fun resolveNdkSymbolUploadTask(variantName: String) = "uploadNdkSymbolFiles${variantName}Release" + private fun resolveMappingUploadTask(variantName: String) = "uploadMapping${variantName.capitalize()}Release" + private fun resolveNdkSymbolUploadTask( + variantName: String + ) = "uploadNdkSymbolFiles${variantName.capitalize()}Release" private fun stubFile(destination: File, content: String) { with(destination.outputStream()) { @@ -1161,9 +1230,28 @@ internal class DdAndroidGradlePluginFunctionalTest { stubFile(cppPlaceholderFile!!, CPP_FILE_CONTENT) } - private fun gradleRunner(configure: GradleRunner.() -> Unit): GradleRunner { + private fun stubGradlePropertiesFile(buildVersionConfig: BuildVersionConfig) { + stubFile( + gradlePropertiesFile, + GRADLE_PROPERTIES_FILE_CONTENT.format( + Locale.US, + buildVersionConfig.agpVersion, + buildVersionConfig.buildToolsVersion, + buildVersionConfig.targetSdkVersion, + buildVersionConfig.kotlinVersion, + PluginUnderTestMetadataReading.readImplementationClasspath() + .joinToString(",") { it.absolutePath }, + buildVersionConfig.jvmTarget + ) + ) + } + + private fun gradleRunner( + gradleVersion: String? = null, + configure: GradleRunner.() -> Unit + ): GradleRunner { return GradleRunner.create() - .withGradleVersion(buildVersionConfig.gradleVersion) + .withGradleVersion(gradleVersion ?: buildVersionConfig.gradleVersion) .withProjectDir(testProjectDir) // https://github.com/gradle/gradle/issues/22466 // for now the workaround will be to manually inject necessary files into plugin classpath @@ -1228,7 +1316,12 @@ internal class DdAndroidGradlePluginFunctionalTest { val jvmTarget: String ) - val SUPPORTED_ABIS = setOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64") + val SUPPORTED_ABIS = mapOf( + "armeabi-v7a" to "arm", + "arm64-v8a" to "arm64", + "x86" to "x86", + "x86_64" to "x64" + ) val APPLICATION_CLASS_CONTENT = """ package com.datadog.android.sample @@ -1288,7 +1381,7 @@ internal class DdAndroidGradlePluginFunctionalTest { buildToolsVersion = buildToolsVersion // some AndroidX dependencies in recent SDK versions require compileSdk >= 33, so downgrading datadogSdkDependency = targetSdkVersion >= 33 ? - "com.datadoghq:dd-sdk-android-rum:2.7.1" : "com.datadoghq:dd-sdk-android:1.15.0" + "com.datadoghq:dd-sdk-android-rum:2.10.0" : "com.datadoghq:dd-sdk-android:1.15.0" jvmTarget = jvmTarget } repositories { @@ -1346,8 +1439,17 @@ internal class DdAndroidGradlePluginFunctionalTest { } """.trimIndent() - const val LATEST_GRADLE_VERSION = "8.6" - const val LATEST_AGP_VERSION = "8.3.1" + const val LATEST_GRADLE_VERSION = "8.7" + const val LATEST_AGP_VERSION = "8.4.1" + + val LATEST_VERSIONS_TEST_CONFIGURATION = BuildVersionConfig( + agpVersion = LATEST_AGP_VERSION, + gradleVersion = LATEST_GRADLE_VERSION, + buildToolsVersion = "34.0.0", + targetSdkVersion = "34", + kotlinVersion = "1.9.23", + jvmTarget = JavaVersion.VERSION_17.toString() + ) // NB: starting from AGP 7.x, Gradle should have the same major version. // While work with Gradle with higher major version is possible, it is not guaranteed. @@ -1360,14 +1462,7 @@ internal class DdAndroidGradlePluginFunctionalTest { kotlinVersion = "1.6.10", jvmTarget = JavaVersion.VERSION_11.toString() ), - BuildVersionConfig( - agpVersion = LATEST_AGP_VERSION, - gradleVersion = LATEST_GRADLE_VERSION, - buildToolsVersion = "34.0.0", - targetSdkVersion = "34", - kotlinVersion = "1.9.23", - jvmTarget = JavaVersion.VERSION_17.toString() - ) + LATEST_VERSIONS_TEST_CONFIGURATION ) const val BUILD_ID_FILE_PATH_APK = "assets/datadog.buildId" diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginTest.kt index 03d77b04..3ca56607 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdAndroidGradlePluginTest.kt @@ -6,34 +6,30 @@ package com.datadog.gradle.plugin -import com.android.build.gradle.AppExtension -import com.android.build.gradle.api.AndroidSourceDirectorySet -import com.android.build.gradle.api.AndroidSourceSet -import com.android.build.gradle.api.ApplicationVariant -import com.android.build.gradle.tasks.ExternalNativeBuildTask -import com.android.builder.model.BuildType -import com.android.builder.model.ProductFlavor import com.datadog.gradle.plugin.internal.ApiKey import com.datadog.gradle.plugin.internal.ApiKeySource import com.datadog.gradle.plugin.internal.GitRepositoryDetector +import com.datadog.gradle.plugin.internal.variant.AppVariant import com.datadog.gradle.plugin.utils.capitalizeChar +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.Case import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.AdvancedForgery import fr.xgouchet.elmyr.annotation.BoolForgery import fr.xgouchet.elmyr.annotation.Forgery +import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.MapForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.annotation.StringForgeryType import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat -import org.gradle.api.NamedDomainObjectContainer import org.gradle.api.Project import org.gradle.api.Transformer import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.Directory +import org.gradle.api.file.RegularFile import org.gradle.api.provider.Provider -import org.gradle.api.provider.ProviderFactory import org.gradle.api.tasks.TaskProvider import org.gradle.testfixtures.ProjectBuilder import org.junit.jupiter.api.BeforeEach @@ -44,14 +40,14 @@ import org.mockito.Mock import org.mockito.junit.jupiter.MockitoExtension import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any -import org.mockito.kotlin.doAnswer import org.mockito.kotlin.doReturn import org.mockito.kotlin.mock +import org.mockito.kotlin.never +import org.mockito.kotlin.verify import org.mockito.kotlin.whenever import org.mockito.quality.Strictness import java.io.File import java.util.UUID -import java.util.concurrent.Callable @Extensions( ExtendWith(MockitoExtension::class), @@ -66,10 +62,7 @@ internal class DdAndroidGradlePluginTest { lateinit var fakeProject: Project @Mock - lateinit var mockVariant: ApplicationVariant - - @Mock - lateinit var mockBuildType: BuildType + lateinit var mockVariant: AppVariant lateinit var fakeBuildId: String @@ -93,14 +86,9 @@ internal class DdAndroidGradlePluginTest { fakeFlavorNames = fakeFlavorNames.take(5) // A D F G A♭ A A♭ G F fakeBuildId = forge.getForgery().toString() fakeProject = ProjectBuilder.builder().build() - val mockProviderFactory = mock() - whenever(mockProviderFactory.provider(any>())) doAnswer { - val argument = it.getArgument>(0) - fakeProject.provider(argument) - } testedPlugin = DdAndroidGradlePlugin( execOps = mock(), - providerFactory = mockProviderFactory + providerFactory = fakeProject.providers ) setEnv(DdAndroidGradlePlugin.DD_API_KEY, "") @@ -114,6 +102,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given @@ -123,11 +112,12 @@ internal class DdAndroidGradlePluginTest { whenever(mockVariant.name) doReturn "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn true - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When val task = testedPlugin.configureVariantForUploadTask( @@ -139,7 +129,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -147,11 +137,12 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(versionName) - assertThat(task.serviceName).isEqualTo(packageName) + assertThat(task.versionName.get()).isEqualTo(versionName) + assertThat(task.serviceName.get()).isEqualTo(packageName) assertThat(task.site).isEqualTo(fakeExtension.site) assertThat(task.remoteRepositoryUrl).isEqualTo(fakeExtension.remoteRepositoryUrl) - assertThat(task.mappingFilePath).isEqualTo(fakeExtension.mappingFilePath) + assertThat(task.mappingFile.get().asFile) + .isEqualTo(File(fakeProject.projectDir, fakeExtension.mappingFilePath)) assertThat(task.mappingFilePackagesAliases) .isEqualTo(fakeExtension.mappingFilePackageAliases) assertThat(task.datadogCiFile).isNull() @@ -163,17 +154,19 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn true - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When val task = testedPlugin.configureVariantForUploadTask( @@ -185,7 +178,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -193,11 +186,12 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(fakeExtension.versionName) - assertThat(task.serviceName).isEqualTo(fakeExtension.serviceName) + assertThat(task.versionName.get()).isEqualTo(fakeExtension.versionName) + assertThat(task.serviceName.get()).isEqualTo(fakeExtension.serviceName) assertThat(task.site).isEqualTo(fakeExtension.site) assertThat(task.remoteRepositoryUrl).isEqualTo(fakeExtension.remoteRepositoryUrl) - assertThat(task.mappingFilePath).isEqualTo(fakeExtension.mappingFilePath) + assertThat(task.mappingFile.get().asFile) + .isEqualTo(File(fakeProject.projectDir, fakeExtension.mappingFilePath)) assertThat(task.mappingFilePackagesAliases) .isEqualTo(fakeExtension.mappingFilePackageAliases) assertThat(task.mappingFileTrimIndents) @@ -211,6 +205,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String, forge: Forge ) { @@ -218,11 +213,12 @@ internal class DdAndroidGradlePluginTest { val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn true - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() val aliasToFilterOut = packageName.substring(0, forge.anInt(min = 1, max = packageName.length + 1)) to @@ -243,7 +239,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -251,11 +247,12 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(fakeExtension.versionName) - assertThat(task.serviceName).isEqualTo(fakeExtension.serviceName) + assertThat(task.versionName.get()).isEqualTo(fakeExtension.versionName) + assertThat(task.serviceName.get()).isEqualTo(fakeExtension.serviceName) assertThat(task.site).isEqualTo(fakeExtension.site) assertThat(task.remoteRepositoryUrl).isEqualTo(fakeExtension.remoteRepositoryUrl) - assertThat(task.mappingFilePath).isEqualTo(fakeExtension.mappingFilePath) + assertThat(task.mappingFile.get().asFile) + .isEqualTo(File(fakeProject.projectDir, fakeExtension.mappingFilePath)) assertThat(task.mappingFilePackagesAliases) .isEqualTo(fakeExtension.mappingFilePackageAliases.minus(aliasToFilterOut.first)) assertThat(task.mappingFileTrimIndents) @@ -269,6 +266,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given @@ -283,11 +281,13 @@ internal class DdAndroidGradlePluginTest { val fakeMappingFilePath = "${fakeProject.buildDir}/outputs/mapping/$variantName/mapping.txt" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn true - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.mappingFile) doReturn fakeMappingFilePath.asFileProvider() + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When val task = testedPlugin.configureVariantForUploadTask( @@ -299,7 +299,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -307,11 +307,11 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(versionName) - assertThat(task.serviceName).isEqualTo(packageName) + assertThat(task.versionName.get()).isEqualTo(versionName) + assertThat(task.serviceName.get()).isEqualTo(packageName) assertThat(task.remoteRepositoryUrl).isEmpty() assertThat(task.site).isEqualTo("") - assertThat(task.mappingFilePath).isEqualTo(fakeMappingFilePath) + assertThat(task.mappingFile.get().asFile.path).isEqualTo(fakeMappingFilePath) assertThat(task.mappingFilePackagesAliases).isEmpty() assertThat(task.mappingFileTrimIndents).isFalse assertThat(task.datadogCiFile).isNull() @@ -323,6 +323,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given @@ -338,11 +339,13 @@ internal class DdAndroidGradlePluginTest { val fakeMappingFilePath = "${fakeProject.buildDir}/outputs/mapping/$variantName/mapping.txt" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn true - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.mappingFile) doReturn fakeMappingFilePath.asFileProvider() + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() val fakeDatadogCiFile = File(fakeProject.projectDir, "datadog-ci.json") fakeDatadogCiFile.createNewFile() @@ -357,7 +360,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -365,11 +368,11 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(versionName) - assertThat(task.serviceName).isEqualTo(packageName) + assertThat(task.versionName.get()).isEqualTo(versionName) + assertThat(task.serviceName.get()).isEqualTo(packageName) assertThat(task.remoteRepositoryUrl).isEmpty() assertThat(task.site).isEqualTo("") - assertThat(task.mappingFilePath).isEqualTo(fakeMappingFilePath) + assertThat(task.mappingFile.get().asFile.path).isEqualTo(fakeMappingFilePath) assertThat(task.mappingFilePackagesAliases).isEmpty() assertThat(task.mappingFileTrimIndents).isFalse assertThat(task.datadogCiFile).isEqualTo(fakeDatadogCiFile) @@ -381,6 +384,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given @@ -396,11 +400,13 @@ internal class DdAndroidGradlePluginTest { val fakeMappingFilePath = "${fakeProject.buildDir}/outputs/mapping/$variantName/mapping.txt" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn true - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.mappingFile) doReturn fakeMappingFilePath.asFileProvider() + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() val fakeDatadogCiFile = File(fakeProject.projectDir, "datadog-ci.json") fakeDatadogCiFile.createNewFile() @@ -415,7 +421,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -423,11 +429,11 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(versionName) - assertThat(task.serviceName).isEqualTo(packageName) + assertThat(task.versionName.get()).isEqualTo(versionName) + assertThat(task.serviceName.get()).isEqualTo(packageName) assertThat(task.remoteRepositoryUrl).isEmpty() assertThat(task.site).isEqualTo("") - assertThat(task.mappingFilePath).isEqualTo(fakeMappingFilePath) + assertThat(task.mappingFile.get().asFile.path).isEqualTo(fakeMappingFilePath) assertThat(task.mappingFilePackagesAliases).isEmpty() assertThat(task.mappingFileTrimIndents).isFalse assertThat(task.datadogCiFile).isNull() @@ -435,29 +441,21 @@ internal class DdAndroidGradlePluginTest { } @Test - fun `M create buildId task W configureTasksForVariant() { no deobfuscation }`( + fun `M not create buildId task W configureTasksForVariant() { no deobfuscation, no native build enabled }`( @StringForgery(case = Case.LOWER) flavorName: String, - @StringForgery(case = Case.LOWER) buildTypeName: String, - @StringForgery versionName: String, - @StringForgery packageName: String + @StringForgery(case = Case.LOWER) buildTypeName: String ) { // Given - val mockAppExtension = mockAppExtension() - val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName - whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockVariant.mergeAssetsProvider) doReturn mock() - whenever(mockVariant.packageApplicationProvider) doReturn mock() - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn false + whenever(mockVariant.isNativeBuildEnabled) doReturn false + whenever(mockVariant.flavors) doReturn fakeFlavorNames + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName // When testedPlugin.configureTasksForVariant( fakeProject, - mockAppExtension, fakeExtension, mockVariant, fakeApiKey @@ -465,40 +463,34 @@ internal class DdAndroidGradlePluginTest { // Then val allTasks = fakeProject.tasks.map { it.name } - assertThat(allTasks).contains("generateBuildId${variantName.replaceFirstChar { capitalizeChar(it) }}") + assertThat(allTasks).doesNotContain("generateBuildId${variantName.replaceFirstChar { capitalizeChar(it) }}") + verify(mockVariant, never()).bindWith(any>(), any>()) } @Test - fun `M create uploadSymbol task W configureTasksForVariant() { native build providers }`( + fun `M create uploadNdkSymbolFiles task W configureTasksForVariant() { native build providers }`( @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, + @IntForgery(min = 0) versionCode: Int, @StringForgery versionName: String, @StringForgery packageName: String ) { // Given - val mockAppExtension = mockAppExtension() - val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.isNativeBuildEnabled) doReturn true + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.flavors) doReturn fakeFlavorNames + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockVariant.mergeAssetsProvider) doReturn mock() - whenever(mockVariant.packageApplicationProvider) doReturn mock() - whenever(mockBuildType.name) doReturn fakeBuildTypeName - - val nativeBuildProviders = listOf( - mock>().apply { - whenever(flatMap(any, ExternalNativeBuildTask>>())) doReturn mock() - } - ) - whenever(mockVariant.externalNativeBuildProviders) doReturn nativeBuildProviders + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When testedPlugin.configureTasksForVariant( fakeProject, - mockAppExtension, fakeExtension, mockVariant, fakeApiKey @@ -507,34 +499,33 @@ internal class DdAndroidGradlePluginTest { // Then val allTasks = fakeProject.tasks.map { it.name } assertThat(allTasks).contains("uploadNdkSymbolFiles${variantName.replaceFirstChar { capitalizeChar(it) }}") + verify(mockVariant).bindWith(any()) } @Test - fun `M not create uploadSymbol task W configureTasksForVariant() { no native build providers }`( + fun `M not create uploadNdkSymbolFiles task W configureTasksForVariant() { no native build providers }`( @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, + @IntForgery(min = 0) versionCode: Int, @StringForgery versionName: String, @StringForgery packageName: String ) { // Given - val mockAppExtension = mockAppExtension() - val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockVariant.mergeAssetsProvider) doReturn mock() - whenever(mockVariant.packageApplicationProvider) doReturn mock() - whenever(mockBuildType.name) doReturn fakeBuildTypeName - - whenever(mockVariant.externalNativeBuildProviders) doReturn listOf() + whenever(mockVariant.isMinifyEnabled) doReturn true + whenever(mockVariant.isNativeBuildEnabled) doReturn false + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.flavors) doReturn fakeFlavorNames + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When testedPlugin.configureTasksForVariant( fakeProject, - mockAppExtension, fakeExtension, mockVariant, fakeApiKey @@ -543,33 +534,30 @@ internal class DdAndroidGradlePluginTest { // Then val allTasks = fakeProject.tasks.map { it.name } assertThat(allTasks).allMatch { !it.startsWith("uploadNdkSymbolFiles") } + verify(mockVariant, never()).bindWith(any()) } @Test fun `M not create mapping upload task W configureTasksForVariant() { no deobfuscation }`( @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, - @StringForgery versionName: String, - @StringForgery packageName: String + @IntForgery(min = 0) versionCode: Int, + @StringForgery versionName: String ) { // Given - val mockAppExtension = mockAppExtension() - val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName - whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.name) doReturn fakeBuildTypeName - whenever(mockVariant.mergeAssetsProvider) doReturn mock() - whenever(mockVariant.packageApplicationProvider) doReturn mock() - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.isMinifyEnabled) doReturn false + whenever(mockVariant.isNativeBuildEnabled) doReturn true + whenever(mockVariant.flavors) doReturn fakeFlavorNames + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When testedPlugin.configureTasksForVariant( fakeProject, - mockAppExtension, fakeExtension, mockVariant, fakeApiKey @@ -585,6 +573,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given @@ -592,11 +581,12 @@ internal class DdAndroidGradlePluginTest { val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.isMinifyEnabled) doReturn false - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.isMinifyEnabled) doReturn false + whenever(mockVariant.collectJavaAndKotlinSourceDirectories()) doReturn emptyList().asProvider() // When val task = testedPlugin.configureVariantForUploadTask( @@ -608,7 +598,7 @@ internal class DdAndroidGradlePluginTest { ).get() // Then - check(task is DdMappingFileUploadTask) + check(task is MappingFileUploadTask) assertThat(task.repositoryDetector).isInstanceOf(GitRepositoryDetector::class.java) assertThat(task.name).isEqualTo( "uploadMapping${variantName.replaceFirstChar { capitalizeChar(it) }}" @@ -616,11 +606,12 @@ internal class DdAndroidGradlePluginTest { assertThat(task.apiKey).isEqualTo(fakeApiKey.value) assertThat(task.apiKeySource).isEqualTo(fakeApiKey.source) assertThat(task.variantName).isEqualTo(flavorName) - assertThat(task.versionName).isEqualTo(fakeExtension.versionName) - assertThat(task.serviceName).isEqualTo(fakeExtension.serviceName) + assertThat(task.versionName.get()).isEqualTo(fakeExtension.versionName) + assertThat(task.serviceName.get()).isEqualTo(fakeExtension.serviceName) assertThat(task.site).isEqualTo(fakeExtension.site) assertThat(task.remoteRepositoryUrl).isEqualTo(fakeExtension.remoteRepositoryUrl) - assertThat(task.mappingFilePath).isEqualTo(fakeExtension.mappingFilePath) + assertThat(task.mappingFile.get().asFile) + .isEqualTo(File(fakeProject.projectDir, fakeExtension.mappingFilePath)) assertThat(task.mappingFilePackagesAliases) .isEqualTo(fakeExtension.mappingFilePackageAliases) assertThat(task.mappingFileTrimIndents) @@ -704,8 +695,11 @@ internal class DdAndroidGradlePluginTest { @Test fun `M return default config W resolveExtensionConfiguration() {no variant config}`() { + // Given + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames + // When - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) val config = testedPlugin.resolveExtensionConfiguration(fakeExtension, mockVariant) // Then @@ -728,8 +722,10 @@ internal class DdAndroidGradlePluginTest { fun `M return config W resolveExtensionConfiguration() { variant w full config }`( @Forgery variantConfig: DdExtensionConfiguration ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames whenever(fakeExtension.variants.findByName(variantName)) doReturn variantConfig // When @@ -755,8 +751,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w version only }`( @StringForgery versionName: String ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.versionName = versionName } @@ -782,8 +780,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w service only }`( @StringForgery serviceName: String ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.serviceName = serviceName } @@ -809,8 +809,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w mappingPath }`( @StringForgery(regex = "/([a-z]+)/([a-z]+)/([a-z]+)/mapping.txt") mappingFilePath: String ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.mappingFilePath = mappingFilePath } @@ -841,8 +843,10 @@ internal class DdAndroidGradlePluginTest { value = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)]) ) mappingFilePackageAliases: Map ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.mappingFilePackageAliases = mappingFilePackageAliases } @@ -869,8 +873,10 @@ internal class DdAndroidGradlePluginTest { fun `M return config W resolveExtensionConfiguration() { variant+mappingFileTrimIndents }`( @BoolForgery mappingFileTrimIndents: Boolean ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.mappingFileTrimIndents = mappingFileTrimIndents } @@ -898,8 +904,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w site only }`( @Forgery site: DatadogSite ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.site = site.name } @@ -926,8 +934,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w sdkCheck only }`( @Forgery sdkCheckLevel: SdkCheckLevel ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.checkProjectDependencies = sdkCheckLevel } @@ -952,8 +962,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w remoteUrl only }`( @Forgery fakeConfig: DdExtensionConfiguration ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.remoteRepositoryUrl = fakeConfig.remoteRepositoryUrl } @@ -980,8 +992,10 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant + ignoreDdConfig }`( @Forgery fakeConfig: DdExtensionConfiguration ) { + // Given val variantName = fakeFlavorNames.variantName() - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames val incompleteConfig = DdExtensionConfiguration().apply { this.ignoreDatadogCiFileConfig = fakeConfig.ignoreDatadogCiFileConfig } @@ -1014,6 +1028,7 @@ internal class DdAndroidGradlePluginTest { @Forgery variantConfigB: DdExtensionConfiguration, @Forgery variantConfigC: DdExtensionConfiguration ) { + // Given val flavorNames = listOf(flavorA, flavorB, flavorC) variantConfigA.apply { versionName = null @@ -1024,7 +1039,8 @@ internal class DdAndroidGradlePluginTest { checkProjectDependencies = null } variantConfigC.apply { site = null } - mockVariant.mockFlavors(flavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn flavorNames whenever(fakeExtension.variants.findByName(flavorA)) doReturn variantConfigA whenever(fakeExtension.variants.findByName(flavorB)) doReturn variantConfigB whenever(fakeExtension.variants.findByName(flavorC)) doReturn variantConfigC @@ -1057,12 +1073,14 @@ internal class DdAndroidGradlePluginTest { @Forgery variantConfigAC: DdExtensionConfiguration, @Forgery variantConfigBC: DdExtensionConfiguration ) { + // Given val flavorNames = listOf(flavorA, flavorB, flavorC) variantConfigAB.apply { versionName = null } variantConfigAC.apply { serviceName = null } variantConfigBC.apply { site = null } variantConfigBC.apply { checkProjectDependencies = null } - mockVariant.mockFlavors(flavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn flavorNames whenever( fakeExtension.variants.findByName( flavorA + flavorB.replaceFirstChar { capitalizeChar(it) } @@ -1105,9 +1123,11 @@ internal class DdAndroidGradlePluginTest { fun `M return combined config W resolveExtensionConfiguration() { variant w build type }`( @Forgery configuration: DdExtensionConfiguration ) { + // Given val variantName = fakeFlavorNames.variantName() + fakeBuildTypeName.replaceFirstChar { capitalizeChar(it) } - mockVariant.mockFlavors(fakeFlavorNames, fakeBuildTypeName) + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName + whenever(mockVariant.flavors) doReturn fakeFlavorNames whenever(fakeExtension.variants.findByName(variantName)) doReturn configuration // When @@ -1139,6 +1159,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String, @StringForgery configurationName: String ) { @@ -1147,10 +1168,10 @@ internal class DdAndroidGradlePluginTest { val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName fakeProject.task("compile${variantName.replaceFirstChar { capitalizeChar(it) }}Sources") @@ -1159,13 +1180,14 @@ internal class DdAndroidGradlePluginTest { whenever(mockVariant.compileConfiguration) doReturn mockConfiguration - // When + Then + // When val checkSdkDepsTaskProvider = testedPlugin.configureVariantForSdkCheck( fakeProject, mockVariant, fakeExtension ) + // Then assertThat(checkSdkDepsTaskProvider).isNull() } @@ -1174,6 +1196,7 @@ internal class DdAndroidGradlePluginTest { @StringForgery(case = Case.LOWER) flavorName: String, @StringForgery(case = Case.LOWER) buildTypeName: String, @StringForgery versionName: String, + @IntForgery(min = 1) versionCode: Int, @StringForgery packageName: String ) { // Given @@ -1181,10 +1204,10 @@ internal class DdAndroidGradlePluginTest { val variantName = "$flavorName${buildTypeName.replaceFirstChar { capitalizeChar(it) }}" whenever(mockVariant.name) doReturn variantName whenever(mockVariant.flavorName) doReturn flavorName - whenever(mockVariant.versionName) doReturn versionName - whenever(mockVariant.applicationId) doReturn packageName - whenever(mockVariant.buildType) doReturn mockBuildType - whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockVariant.versionCode) doReturn versionCode.asProvider() + whenever(mockVariant.versionName) doReturn versionName.asProvider() + whenever(mockVariant.applicationId) doReturn packageName.asProvider() + whenever(mockVariant.buildTypeName) doReturn fakeBuildTypeName fakeProject.task("compile${variantName.replaceFirstChar { capitalizeChar(it) }}Sources") @@ -1241,45 +1264,20 @@ internal class DdAndroidGradlePluginTest { return first() + drop(1).joinToString("") { it.replaceFirstChar { capitalizeChar(it) } } } - private fun ApplicationVariant.mockFlavors( - flavorNames: List, - buildTypeName: String - ) { - val mockFlavors: MutableList = mutableListOf() - for (flavorName in flavorNames) { - mockFlavors.add( - mock().apply { - whenever(this.name) doReturn flavorName - } - ) - } - val mockBuildType: BuildType = mock() - whenever(mockBuildType.name) doReturn buildTypeName - - whenever(productFlavors) doReturn mockFlavors - whenever(buildType) doReturn mockBuildType - } - private fun mockBuildIdGenerationTask(buildId: String): TaskProvider { return mock>().apply { - val mockBuildIdProvider = mock>().apply { - whenever(get()) doReturn buildId - } whenever( flatMap(any, GenerateBuildIdTask>>()) - ) doReturn mockBuildIdProvider + ) doReturn buildId.asProvider() } } - private fun mockAppExtension(): AppExtension { - val mockAppExtension: AppExtension = mock() - val mockSoureSetsContainer: NamedDomainObjectContainer = mock() - val mockSoureSets: AndroidSourceSet = mock() - val mockAssets: AndroidSourceDirectorySet = mock() - whenever(mockAppExtension.sourceSets).thenReturn(mockSoureSetsContainer) - whenever(mockSoureSetsContainer.getByName(any())).thenReturn(mockSoureSets) - whenever(mockSoureSets.assets).thenReturn(mockAssets) - return mockAppExtension + private fun T.asProvider(): Provider { + return fakeProject.provider { this } + } + + private fun String.asFileProvider(): Provider { + return fakeProject.objects.fileProperty().value { File(this) } } // endregion diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdMappingFileUploadTaskTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/MappingFileUploadTaskTest.kt similarity index 83% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdMappingFileUploadTaskTest.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/MappingFileUploadTaskTest.kt index e99ba6ff..923956c7 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdMappingFileUploadTaskTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/MappingFileUploadTaskTest.kt @@ -10,6 +10,7 @@ import com.datadog.gradle.plugin.internal.ApiKey import com.datadog.gradle.plugin.internal.ApiKeySource import com.datadog.gradle.plugin.internal.DdAppIdentifier import com.datadog.gradle.plugin.internal.Uploader +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery @@ -17,7 +18,7 @@ import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions.assertThat -import org.gradle.api.provider.Provider +import org.gradle.api.Project import org.gradle.testfixtures.ProjectBuilder import org.json.JSONArray import org.json.JSONObject @@ -36,7 +37,6 @@ import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq -import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @@ -50,9 +50,11 @@ import java.util.UUID ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class DdMappingFileUploadTaskTest { +internal class MappingFileUploadTaskTest { - private lateinit var testedTask: DdMappingFileUploadTask + private lateinit var testedTask: MappingFileUploadTask + + private lateinit var fakeProject: Project @TempDir lateinit var tempDir: File @@ -99,13 +101,13 @@ internal class DdMappingFileUploadTaskTest { @BeforeEach fun `set up`(forge: Forge) { - val fakeProject = ProjectBuilder.builder() + fakeProject = ProjectBuilder.builder() .withProjectDir(tempDir) .build() testedTask = fakeProject.tasks.create( - "DdMappingFileUploadTask", - DdMappingFileUploadTask::class.java, + "MappingFileUploadTask", + MappingFileUploadTask::class.java, mockRepositoryDetector ) @@ -118,22 +120,18 @@ internal class DdMappingFileUploadTaskTest { testedTask.apiKey = fakeApiKey.value testedTask.apiKeySource = fakeApiKey.source testedTask.variantName = fakeVariant - testedTask.versionName = fakeVersion - testedTask.versionCode = mock>().apply { - whenever(get()) doReturn fakeVersionCode - } - testedTask.serviceName = fakeService + testedTask.versionName.set(fakeVersion) + testedTask.versionCode.set(fakeVersionCode) + testedTask.serviceName.set(fakeService) testedTask.site = fakeSite.name - testedTask.buildId = mock>().apply { - whenever(isPresent) doReturn true - whenever(get()) doReturn fakeBuildId - } - setEnv(DdFileUploadTask.DATADOG_SITE, "") + testedTask.buildId.set(fakeBuildId) + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(tempDir, fakeMappingFileName))) + setEnv(FileUploadTask.DATADOG_SITE, "") } @AfterEach fun `tear down`() { - removeEnv(DdFileUploadTask.DATADOG_SITE) + removeEnv(FileUploadTask.DATADOG_SITE) } @Test @@ -141,7 +139,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile whenever(mockRepositoryDetector.detectRepositories(any(), eq(""))) @@ -154,11 +152,11 @@ internal class DdMappingFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE, + fileKey = MappingFileUploadTask.KEY_JVM_MAPPING_FILE, file = fakeMappingFile, - encoding = DdMappingFileUploadTask.MEDIA_TYPE_TXT, - fileType = DdMappingFileUploadTask.TYPE_JVM_MAPPING_FILE, - fileName = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME + encoding = MappingFileUploadTask.MEDIA_TYPE_TXT, + fileType = MappingFileUploadTask.TYPE_JVM_MAPPING_FILE, + fileName = MappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME ), fakeRepositoryFile, fakeApiKey.value, @@ -170,7 +168,8 @@ internal class DdMappingFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) assertThat(fakeRepositoryFile.readText()) .isEqualTo( @@ -179,8 +178,11 @@ internal class DdMappingFileUploadTaskTest { } @Test - fun `M upload file W applyTask() { short aliases requested }`() { + fun `M upload file W applyTask() { short aliases requested }`( + @StringForgery fakeApplicationId: String + ) { // Given + testedTask.applicationId.set(fakeApplicationId) testedTask.mappingFilePackagesAliases = mapOf( "androidx.fragment.app" to "axfraga", "androidx.activity" to "axact", @@ -193,7 +195,7 @@ internal class DdMappingFileUploadTaskTest { val fakeMappingFile = File(tempDir, fakeMappingFileName) fileFromResourcesPath("mapping.txt").copyTo(fakeMappingFile) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile whenever(mockRepositoryDetector.detectRepositories(any(), eq(""))) @@ -219,7 +221,8 @@ internal class DdMappingFileUploadTaskTest { ) ), eq(fakeRepoInfo), - useGzip = eq(true) + useGzip = eq(true), + emulateNetworkCall = eq(false) ) assertThat(lastValue.file).hasSameTextualContentAs( fileFromResourcesPath("mapping-with-aliases.txt") @@ -244,7 +247,7 @@ internal class DdMappingFileUploadTaskTest { ) testedTask.mappingFileTrimIndents = true - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile whenever(mockRepositoryDetector.detectRepositories(any(), eq(""))) @@ -270,7 +273,8 @@ internal class DdMappingFileUploadTaskTest { ) ), eq(fakeRepoInfo), - useGzip = eq(true) + useGzip = eq(true), + emulateNetworkCall = eq(false) ) assertThat(lastValue.file.readLines()).isEqualTo(expectedLines) } @@ -293,14 +297,14 @@ internal class DdMappingFileUploadTaskTest { ) testedTask.mappingFileTrimIndents = true - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile whenever(mockRepositoryDetector.detectRepositories(any(), eq(""))) .doReturn(listOf(fakeRepoInfo)) val oldShrinkedMappingFile = File( fakeMappingFile.parent, - DdMappingFileUploadTask.MAPPING_OPTIMIZED_FILE_NAME + MappingFileUploadTask.MAPPING_OPTIMIZED_FILE_NAME ) oldShrinkedMappingFile.createNewFile() oldShrinkedMappingFile.writeText(forge.aString()) @@ -325,7 +329,8 @@ internal class DdMappingFileUploadTaskTest { ) ), eq(fakeRepoInfo), - useGzip = eq(true) + useGzip = eq(true), + emulateNetworkCall = eq(false) ) assertThat(lastValue.file.readLines()).isEqualTo(expectedLines) } @@ -336,7 +341,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) testedTask.remoteRepositoryUrl = fakeRemoteUrl val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile @@ -350,11 +355,11 @@ internal class DdMappingFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE, + fileKey = MappingFileUploadTask.KEY_JVM_MAPPING_FILE, file = fakeMappingFile, - encoding = DdMappingFileUploadTask.MEDIA_TYPE_TXT, - fileType = DdMappingFileUploadTask.TYPE_JVM_MAPPING_FILE, - fileName = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME + encoding = MappingFileUploadTask.MEDIA_TYPE_TXT, + fileType = MappingFileUploadTask.TYPE_JVM_MAPPING_FILE, + fileName = MappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME ), fakeRepositoryFile, fakeApiKey.value, @@ -366,7 +371,8 @@ internal class DdMappingFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) assertThat(fakeRepositoryFile.readText()) .isEqualTo( @@ -379,7 +385,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile whenever(mockRepositoryDetector.detectRepositories(any(), eq(""))) @@ -392,11 +398,11 @@ internal class DdMappingFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE, + fileKey = MappingFileUploadTask.KEY_JVM_MAPPING_FILE, file = fakeMappingFile, - encoding = DdMappingFileUploadTask.MEDIA_TYPE_TXT, - fileType = DdMappingFileUploadTask.TYPE_JVM_MAPPING_FILE, - fileName = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME + encoding = MappingFileUploadTask.MEDIA_TYPE_TXT, + fileType = MappingFileUploadTask.TYPE_JVM_MAPPING_FILE, + fileName = MappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME ), null, fakeApiKey.value, @@ -408,7 +414,8 @@ internal class DdMappingFileUploadTaskTest { buildId = fakeBuildId ), null, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -417,7 +424,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) testedTask.apiKey = "" testedTask.apiKeySource = ApiKeySource.NONE @@ -427,7 +434,7 @@ internal class DdMappingFileUploadTaskTest { } // Then - assertThat(exception.message).isEqualTo(DdMappingFileUploadTask.API_KEY_MISSING_ERROR) + assertThat(exception.message).isEqualTo(MappingFileUploadTask.API_KEY_MISSING_ERROR) verifyNoInteractions(mockUploader) } @@ -438,7 +445,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) testedTask.apiKey = forge.anAlphaNumericalString().let { val splitIndex = forge.anInt(min = 0, max = it.length) + 1 it.substring(0, splitIndex) + @@ -454,7 +461,7 @@ internal class DdMappingFileUploadTaskTest { // Then assertThat(exception.message) - .isEqualTo(DdMappingFileUploadTask.INVALID_API_KEY_FORMAT_ERROR) + .isEqualTo(MappingFileUploadTask.INVALID_API_KEY_FORMAT_ERROR) verifyNoInteractions(mockUploader) } @@ -463,8 +470,8 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path - whenever(testedTask.buildId.isPresent) doReturn false + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) + testedTask.buildId.set(null as String?) // When val exception = assertThrows { @@ -472,7 +479,7 @@ internal class DdMappingFileUploadTaskTest { } // Then - assertThat(exception.message).isEqualTo(DdMappingFileUploadTask.MISSING_BUILD_ID_ERROR) + assertThat(exception.message).isEqualTo(MappingFileUploadTask.MISSING_BUILD_ID_ERROR) verifyNoInteractions(mockUploader) } @@ -481,9 +488,8 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path - whenever(testedTask.buildId.isPresent) doReturn true - whenever(testedTask.buildId.get()) doReturn "" + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) + testedTask.buildId.set("") // When val exception = assertThrows { @@ -491,7 +497,7 @@ internal class DdMappingFileUploadTaskTest { } // Then - assertThat(exception.message).isEqualTo(DdMappingFileUploadTask.MISSING_BUILD_ID_ERROR) + assertThat(exception.message).isEqualTo(MappingFileUploadTask.MISSING_BUILD_ID_ERROR) verifyNoInteractions(mockUploader) } @@ -504,7 +510,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) testedTask.site = siteName // When @@ -521,7 +527,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.writeText(fakeMappingFileContent) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) val fakeRepositoryFile = File(tempDir, fakeRepositoryFileName) testedTask.repositoryFile = fakeRepositoryFile testedTask.site = "" @@ -535,11 +541,11 @@ internal class DdMappingFileUploadTaskTest { verify(mockUploader).upload( DatadogSite.US1, Uploader.UploadFileInfo( - fileKey = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE, + fileKey = MappingFileUploadTask.KEY_JVM_MAPPING_FILE, file = fakeMappingFile, - encoding = DdMappingFileUploadTask.MEDIA_TYPE_TXT, - fileType = DdMappingFileUploadTask.TYPE_JVM_MAPPING_FILE, - fileName = DdMappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME + encoding = MappingFileUploadTask.MEDIA_TYPE_TXT, + fileType = MappingFileUploadTask.TYPE_JVM_MAPPING_FILE, + fileName = MappingFileUploadTask.KEY_JVM_MAPPING_FILE_NAME ), fakeRepositoryFile, fakeApiKey.value, @@ -551,7 +557,8 @@ internal class DdMappingFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) assertThat(fakeRepositoryFile.readText()) .isEqualTo( @@ -563,7 +570,7 @@ internal class DdMappingFileUploadTaskTest { fun `M do nothing W applyTask() {no mapping file}`() { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) // When testedTask.applyTask() @@ -577,7 +584,7 @@ internal class DdMappingFileUploadTaskTest { // Given val fakeMappingFile = File(tempDir, fakeMappingFileName) fakeMappingFile.mkdirs() - testedTask.mappingFilePath = fakeMappingFile.path + testedTask.mappingFile.set(fakeProject.objects.fileProperty().fileValue(File(fakeMappingFile.path))) // When assertThrows { @@ -752,7 +759,7 @@ internal class DdMappingFileUploadTaskTest { fun `M read site from environment variable W applyTask() {site is not set}`(forge: Forge) { // Given val fakeDatadogEnvDomain = forge.aValueFrom(DatadogSite::class.java).domain - setEnv(DdFileUploadTask.DATADOG_SITE, fakeDatadogEnvDomain) + setEnv(FileUploadTask.DATADOG_SITE, fakeDatadogEnvDomain) testedTask.site = "" // When diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdNdkSymbolFileUploadTaskTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/NdkSymbolFileUploadTaskTest.kt similarity index 86% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdNdkSymbolFileUploadTaskTest.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/NdkSymbolFileUploadTaskTest.kt index a370e738..e26d0cbf 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdNdkSymbolFileUploadTaskTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/NdkSymbolFileUploadTaskTest.kt @@ -1,10 +1,17 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + package com.datadog.gradle.plugin -import com.android.build.gradle.api.ApplicationVariant import com.datadog.gradle.plugin.internal.ApiKey import com.datadog.gradle.plugin.internal.ApiKeySource import com.datadog.gradle.plugin.internal.DdAppIdentifier import com.datadog.gradle.plugin.internal.Uploader +import com.datadog.gradle.plugin.internal.variant.AppVariant +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery @@ -13,7 +20,6 @@ import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension import org.assertj.core.api.Assertions import org.assertj.core.api.Assertions.assertThat -import org.gradle.api.provider.Provider import org.gradle.internal.impldep.org.junit.Assume.assumeTrue import org.gradle.testfixtures.ProjectBuilder import org.json.JSONArray @@ -31,7 +37,6 @@ import org.mockito.junit.jupiter.MockitoSettings import org.mockito.kotlin.any import org.mockito.kotlin.doReturn import org.mockito.kotlin.eq -import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.mockito.kotlin.verifyNoInteractions import org.mockito.kotlin.whenever @@ -45,8 +50,9 @@ import java.util.UUID ) @MockitoSettings(strictness = Strictness.LENIENT) @ForgeConfiguration(Configurator::class) -internal class DdNdkSymbolFileUploadTaskTest { - private lateinit var testedTask: DdNdkSymbolFileUploadTask +internal class NdkSymbolFileUploadTaskTest { + + private lateinit var testedTask: NdkSymbolFileUploadTask @TempDir lateinit var tempDir: File @@ -55,7 +61,7 @@ internal class DdNdkSymbolFileUploadTaskTest { lateinit var mockUploader: Uploader @Mock - lateinit var mockVariant: ApplicationVariant + lateinit var mockVariant: AppVariant @Mock lateinit var mockRepositoryDetector: RepositoryDetector @@ -91,12 +97,12 @@ internal class DdNdkSymbolFileUploadTaskTest { .withProjectDir(tempDir) .build() whenever(mockVariant.flavorName).thenReturn(fakeVariantName) - whenever(mockVariant.versionName).thenReturn(fakeVersion) - whenever(mockVariant.versionCode).thenReturn(fakeVersionCode) + whenever(mockVariant.versionName).thenReturn(fakeProject.provider { fakeVersion }) + whenever(mockVariant.versionCode).thenReturn(fakeProject.provider { fakeVersionCode }) testedTask = fakeProject.tasks.create( - "DdSymbolFileUploadTask", - DdNdkSymbolFileUploadTask::class.java, + "SymbolFileUploadTask", + NdkSymbolFileUploadTask::class.java, mockRepositoryDetector ) testedTask.uploader = mockUploader @@ -107,10 +113,7 @@ internal class DdNdkSymbolFileUploadTaskTest { fakeBuildId = forge.getForgery().toString() testedTask.searchDirectories.from(tempDir) - testedTask.buildId = mock>().apply { - whenever(isPresent) doReturn true - whenever(get()) doReturn fakeBuildId - } + testedTask.buildId.set(fakeBuildId) val fakeConfiguration = with(DdExtensionConfiguration()) { versionName = fakeVersion @@ -123,12 +126,12 @@ internal class DdNdkSymbolFileUploadTaskTest { fakeConfiguration, mockVariant ) - setEnv(DdFileUploadTask.DATADOG_SITE, "") + setEnv(FileUploadTask.DATADOG_SITE, "") } @AfterEach fun `tear down`() { - removeEnv(DdFileUploadTask.DATADOG_SITE) + removeEnv(FileUploadTask.DATADOG_SITE) } @Test @@ -147,13 +150,13 @@ internal class DdNdkSymbolFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdNdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, + fileKey = NdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, file = fakeSoFile, - encoding = DdNdkSymbolFileUploadTask.ENCODING, - fileType = DdNdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, + encoding = NdkSymbolFileUploadTask.ENCODING, + fileType = NdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, fileName = "libfake.so", extraAttributes = mapOf( - "arch" to "arm64-v8a" + "arch" to "arm64" ) ), fakeRepositoryFile, @@ -166,7 +169,8 @@ internal class DdNdkSymbolFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -178,7 +182,7 @@ internal class DdNdkSymbolFileUploadTaskTest { whenever(mockRepositoryDetector.detectRepositories(any(), eq(""))) .doReturn(listOf(fakeRepoInfo)) val fakeSoFiles = mapOf( - "arm64-v8a" to writeFakeSoFile("arm64-v8a"), + "arm64" to writeFakeSoFile("arm64-v8a"), "x86" to writeFakeSoFile("x86") ) @@ -190,10 +194,10 @@ internal class DdNdkSymbolFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdNdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, + fileKey = NdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, file = it.value, - encoding = DdNdkSymbolFileUploadTask.ENCODING, - fileType = DdNdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, + encoding = NdkSymbolFileUploadTask.ENCODING, + fileType = NdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, fileName = "libfake.so", extraAttributes = mapOf( "arch" to it.key @@ -209,7 +213,8 @@ internal class DdNdkSymbolFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } } @@ -234,13 +239,13 @@ internal class DdNdkSymbolFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdNdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, + fileKey = NdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, file = fakeSoFile, - encoding = DdNdkSymbolFileUploadTask.ENCODING, - fileType = DdNdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, + encoding = NdkSymbolFileUploadTask.ENCODING, + fileType = NdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, fileName = "libfake.so", extraAttributes = mapOf( - "arch" to "arm64-v8a" + "arch" to "arm64" ) ), fakeRepositoryFile, @@ -253,7 +258,8 @@ internal class DdNdkSymbolFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) Assertions.assertThat(fakeRepositoryFile.readText()) .isEqualTo( @@ -277,13 +283,13 @@ internal class DdNdkSymbolFileUploadTaskTest { verify(mockUploader).upload( fakeSite, Uploader.UploadFileInfo( - fileKey = DdNdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, + fileKey = NdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, file = fakeSoFile, - encoding = DdNdkSymbolFileUploadTask.ENCODING, - fileType = DdNdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, + encoding = NdkSymbolFileUploadTask.ENCODING, + fileType = NdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, fileName = "libfake.so", extraAttributes = mapOf( - "arch" to "arm64-v8a" + "arch" to "arm64" ) ), null, @@ -296,7 +302,8 @@ internal class DdNdkSymbolFileUploadTaskTest { buildId = fakeBuildId ), null, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -313,7 +320,7 @@ internal class DdNdkSymbolFileUploadTaskTest { } // Then - assertThat(error.message).isEqualTo(DdFileUploadTask.API_KEY_MISSING_ERROR) + assertThat(error.message).isEqualTo(FileUploadTask.API_KEY_MISSING_ERROR) verifyNoInteractions(mockUploader) } @@ -328,7 +335,7 @@ internal class DdNdkSymbolFileUploadTaskTest { forge.anElementFrom("\"", "'") + it.substring(splitIndex) } - writeFakeSoFile("arm64-v8a") + writeFakeSoFile("arm64") // When val exception = assertThrows { @@ -337,14 +344,14 @@ internal class DdNdkSymbolFileUploadTaskTest { // Then assertThat(exception.message) - .isEqualTo(DdFileUploadTask.INVALID_API_KEY_FORMAT_ERROR) + .isEqualTo(FileUploadTask.INVALID_API_KEY_FORMAT_ERROR) verifyNoInteractions(mockUploader) } @Test fun `M throw error W applyTask() {buildId is missing}`() { // Given - whenever(testedTask.buildId.isPresent) doReturn false + testedTask.buildId.set(null as String?) writeFakeSoFile("arm64-v8a") // When @@ -353,15 +360,14 @@ internal class DdNdkSymbolFileUploadTaskTest { } // Then - assertThat(exception.message).isEqualTo(DdFileUploadTask.MISSING_BUILD_ID_ERROR) + assertThat(exception.message).isEqualTo(FileUploadTask.MISSING_BUILD_ID_ERROR) verifyNoInteractions(mockUploader) } @Test fun `M throw error W applyTask() {buildId is empty string}`() { // Given - whenever(testedTask.buildId.isPresent) doReturn true - whenever(testedTask.buildId.get()) doReturn "" + testedTask.buildId.set("") writeFakeSoFile("arm64-v8a") // When @@ -370,7 +376,7 @@ internal class DdNdkSymbolFileUploadTaskTest { } // Then - assertThat(exception.message).isEqualTo(DdFileUploadTask.MISSING_BUILD_ID_ERROR) + assertThat(exception.message).isEqualTo(FileUploadTask.MISSING_BUILD_ID_ERROR) verifyNoInteractions(mockUploader) } @@ -382,7 +388,6 @@ internal class DdNdkSymbolFileUploadTaskTest { // Given testedTask.site = siteName - val fakeSoFile = writeFakeSoFile("arm64-v8a") // When assertThrows { @@ -410,13 +415,13 @@ internal class DdNdkSymbolFileUploadTaskTest { verify(mockUploader).upload( DatadogSite.US1, Uploader.UploadFileInfo( - fileKey = DdNdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, + fileKey = NdkSymbolFileUploadTask.KEY_NDK_SYMBOL_FILE, file = fakeSoFile, - encoding = DdNdkSymbolFileUploadTask.ENCODING, - fileType = DdNdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, + encoding = NdkSymbolFileUploadTask.ENCODING, + fileType = NdkSymbolFileUploadTask.TYPE_NDK_SYMBOL_FILE, fileName = "libfake.so", extraAttributes = mapOf( - "arch" to "arm64-v8a" + "arch" to "arm64" ) ), fakeRepositoryFile, @@ -429,7 +434,8 @@ internal class DdNdkSymbolFileUploadTaskTest { buildId = fakeBuildId ), fakeRepoInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -608,7 +614,7 @@ internal class DdNdkSymbolFileUploadTaskTest { fun `M read site from environment variable W applyTask() {site is not set}`(forge: Forge) { // Given val fakeDatadogEnvDomain = forge.aValueFrom(DatadogSite::class.java).domain - setEnv(DdFileUploadTask.DATADOG_SITE, fakeDatadogEnvDomain) + setEnv(FileUploadTask.DATADOG_SITE, fakeDatadogEnvDomain) testedTask.site = "" // When diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoTest.kt index 9a399a33..fb3615b1 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoTest.kt @@ -6,6 +6,7 @@ package com.datadog.gradle.plugin +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdTaskUtilsTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/TaskUtilsTest.kt similarity index 80% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdTaskUtilsTest.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/TaskUtilsTest.kt index eb4a8ec3..c5903bfb 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdTaskUtilsTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/TaskUtilsTest.kt @@ -1,5 +1,12 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + package com.datadog.gradle.plugin +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.junit5.ForgeConfiguration import fr.xgouchet.elmyr.junit5.ForgeExtension @@ -14,7 +21,8 @@ import java.io.File ExtendWith(ForgeExtension::class) ) @ForgeConfiguration(Configurator::class) -class DdTaskUtilsTest { +class TaskUtilsTest { + @Test fun `M find datadog-ci file W findDatadogCiFile()`( @TempDir rootDir: File, @@ -26,7 +34,7 @@ class DdTaskUtilsTest { File(tree[forge.anInt(0, tree.size)], "datadog-ci.json").createNewFile() // When - val ciFile = DdTaskUtils.findDatadogCiFile(tree.last()) + val ciFile = TaskUtils.findDatadogCiFile(tree.last()) // Then Assertions.assertThat(ciFile).isNotNull() @@ -41,7 +49,7 @@ class DdTaskUtilsTest { val tree = buildDirectoryTree(rootDir, maxDepth = 3, forge = forge) // When - val ciFile = DdTaskUtils.findDatadogCiFile(tree.last()) + val ciFile = TaskUtils.findDatadogCiFile(tree.last()) // Then Assertions.assertThat(ciFile).isNull() @@ -56,7 +64,7 @@ class DdTaskUtilsTest { val tree = buildDirectoryTree(rootDir, minDepth = 4, maxDepth = 7, forge = forge) // When - val ciFile = DdTaskUtils.findDatadogCiFile(tree.last()) + val ciFile = TaskUtils.findDatadogCiFile(tree.last()) // Then Assertions.assertThat(ciFile).isNull() diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/GitRepositoryDetectorTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/GitRepositoryDetectorTest.kt index 1ee7fd2a..af8cc7fa 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/GitRepositoryDetectorTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/GitRepositoryDetectorTest.kt @@ -6,9 +6,9 @@ package com.datadog.gradle.plugin.internal -import com.datadog.gradle.plugin.Configurator import com.datadog.gradle.plugin.RepositoryDetector import com.datadog.gradle.plugin.internal.sanitizer.UrlSanitizer +import com.datadog.gradle.plugin.utils.forge.Configurator import com.datadog.gradle.plugin.utils.initializeGit import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.StringForgery diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploaderTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploaderTest.kt index c6a84952..ae0c0ead 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploaderTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/OkHttpUploaderTest.kt @@ -6,10 +6,10 @@ package com.datadog.gradle.plugin.internal -import com.datadog.gradle.plugin.Configurator import com.datadog.gradle.plugin.DatadogSite -import com.datadog.gradle.plugin.RecordedRequestAssert.Companion.assertThat import com.datadog.gradle.plugin.RepositoryInfo +import com.datadog.gradle.plugin.utils.assertj.RecordedRequestAssert.Companion.assertThat +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.annotation.Forgery import fr.xgouchet.elmyr.annotation.IntForgery @@ -164,7 +164,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) // Then @@ -209,7 +210,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = false + useGzip = false, + emulateNetworkCall = false ) // Then @@ -254,7 +256,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, null, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) // Then @@ -295,7 +298,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -348,7 +352,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -398,7 +403,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -464,7 +470,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -518,7 +525,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -571,7 +579,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } @@ -621,7 +630,8 @@ internal class OkHttpUploaderTest { fakeApiKey, fakeIdentifier, fakeRepositoryInfo, - useGzip = true + useGzip = true, + emulateNetworkCall = false ) } assertThat(exception.message).isEqualTo( diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/VariantIteratorTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/VariantIteratorTest.kt index 5ab81e98..47068db9 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/VariantIteratorTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/VariantIteratorTest.kt @@ -6,8 +6,8 @@ package com.datadog.gradle.plugin.internal -import com.datadog.gradle.plugin.Configurator import com.datadog.gradle.plugin.utils.capitalizeChar +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.Case import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/sanitizer/GitRemoteUrlSanitizerTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/sanitizer/GitRemoteUrlSanitizerTest.kt index 35962daf..d1d348d2 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/sanitizer/GitRemoteUrlSanitizerTest.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/sanitizer/GitRemoteUrlSanitizerTest.kt @@ -6,7 +6,7 @@ package com.datadog.gradle.plugin.internal.sanitizer -import com.datadog.gradle.plugin.Configurator +import com.datadog.gradle.plugin.utils.forge.Configurator import fr.xgouchet.elmyr.annotation.IntForgery import fr.xgouchet.elmyr.annotation.StringForgery import fr.xgouchet.elmyr.junit5.ForgeConfiguration diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/variant/LegacyApiAppVariantTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/variant/LegacyApiAppVariantTest.kt new file mode 100644 index 00000000..e7ff2597 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/variant/LegacyApiAppVariantTest.kt @@ -0,0 +1,330 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal.variant + +import com.android.build.gradle.AppExtension +import com.android.build.gradle.api.ApplicationVariant +import com.android.build.gradle.tasks.ExternalNativeBuildTask +import com.android.builder.model.BuildType +import com.android.builder.model.ProductFlavor +import com.android.builder.model.SourceProvider +import com.datadog.gradle.plugin.utils.forge.Configurator +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.BoolForgery +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.gradle.api.artifacts.Configuration +import org.gradle.api.tasks.TaskProvider +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.api.io.TempDir +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.io.File +import java.nio.file.Paths + +@Extensions( + ExtendWith(ForgeExtension::class), + ExtendWith(MockitoExtension::class) +) +@ForgeConfiguration(value = Configurator::class) +@MockitoSettings(strictness = Strictness.LENIENT) +internal class LegacyApiAppVariantTest { + + private lateinit var testedAppVariant: LegacyApiAppVariant + + private val fakeProject = ProjectBuilder.builder().build() + + @Mock + lateinit var mockAppExtension: AppExtension + + @Mock + lateinit var mockAndroidVariant: ApplicationVariant + + @BeforeEach + fun `set up`() { + testedAppVariant = LegacyApiAppVariant( + mockAndroidVariant, + mockAppExtension, + fakeProject + ) + } + + @Test + fun `M return variant name W name`( + @StringForgery fakeName: String + ) { + // Given + whenever(mockAndroidVariant.name) doReturn fakeName + + // When + val name = testedAppVariant.name + + // Then + assertThat(name).isEqualTo(fakeName) + } + + @Test + fun `M return application ID W applicationId`( + @StringForgery fakeApplicationId: String + ) { + // Given + whenever(mockAndroidVariant.applicationId) doReturn fakeApplicationId + + // When + val applicationId = testedAppVariant.applicationId.get() + + // Then + assertThat(applicationId).isEqualTo(fakeApplicationId) + } + + @Test + fun `M return flavor name W flavorName`( + @StringForgery fakeFlavorName: String + ) { + // Given + whenever(mockAndroidVariant.flavorName) doReturn fakeFlavorName + + // When + val flavorName = testedAppVariant.flavorName + + // Then + assertThat(flavorName).isEqualTo(fakeFlavorName) + } + + @Test + fun `M return version name W versionName`( + @StringForgery fakeVersionName: String + ) { + // Given + whenever(mockAndroidVariant.versionName) doReturn fakeVersionName + + // When + val versionName = testedAppVariant.versionName.get() + + // Then + assertThat(versionName).isEqualTo(fakeVersionName) + } + + @Test + fun `M return empty version name W versionName { missing version name }`() { + // Given + whenever(mockAndroidVariant.versionName) doReturn null + + // When + val versionName = testedAppVariant.versionName.get() + + // Then + assertThat(versionName).isEmpty() + } + + @Test + fun `M return version code W versionCode`( + @IntForgery(min = 1) fakeVersionCode: Int + ) { + // Given + whenever(mockAndroidVariant.versionCode) doReturn fakeVersionCode + + // When + val versionCode = testedAppVariant.versionCode.get() + + // Then + assertThat(versionCode).isEqualTo(fakeVersionCode) + } + + @Test + fun `M return compile configuration W compileConfiguration`() { + // Given + val mockCompileConfiguration = mock() + whenever(mockAndroidVariant.compileConfiguration) doReturn mockCompileConfiguration + + // When + val compileConfiguration = testedAppVariant.compileConfiguration + + // Then + assertThat(compileConfiguration).isSameAs(mockCompileConfiguration) + } + + @Test + fun `M return true W isNativeBuildEnabled { native build providers registered }`( + forge: Forge + ) { + // Given + val externalNativeBuildProviders = forge.aList { mock>() } + whenever(mockAndroidVariant.externalNativeBuildProviders) doReturn externalNativeBuildProviders + + // When + val isNativeBuildEnabled = testedAppVariant.isNativeBuildEnabled + + // Then + assertThat(isNativeBuildEnabled).isTrue() + } + + @Test + fun `M return false W isNativeBuildEnabled { native build providers not registered }`() { + // Given + whenever(mockAndroidVariant.externalNativeBuildProviders) doReturn emptyList() + + // When + val isNativeBuildEnabled = testedAppVariant.isNativeBuildEnabled + + // Then + assertThat(isNativeBuildEnabled).isFalse() + } + + @Test + fun `M return if minification is enabled or not W isMinifyEnabled`( + @BoolForgery fakeMinifyEnabled: Boolean + ) { + // Given + val mockBuildType = mock() + whenever(mockBuildType.isMinifyEnabled) doReturn fakeMinifyEnabled + whenever(mockAndroidVariant.buildType) doReturn mockBuildType + + // When + val isMinifyEnabled = testedAppVariant.isMinifyEnabled + + // Then + assertThat(isMinifyEnabled).isEqualTo(fakeMinifyEnabled) + } + + @Test + fun `M return build type name W buildTypeName`( + @StringForgery fakeBuildTypeName: String + ) { + // Given + val mockBuildType = mock() + whenever(mockBuildType.name) doReturn fakeBuildTypeName + whenever(mockAndroidVariant.buildType) doReturn mockBuildType + + // When + val buildTypeName = testedAppVariant.buildTypeName + + // Then + assertThat(buildTypeName).isEqualTo(fakeBuildTypeName) + } + + @Test + fun `M return flavor names W flavors`( + @StringForgery fakeFlavorNames: List + ) { + // Given + val mockFlavors = fakeFlavorNames.map { + val mockFlavor = mock() + whenever(mockFlavor.name) doReturn it + mockFlavor + } + whenever(mockAndroidVariant.productFlavors) doReturn mockFlavors + + // When + val flavors = testedAppVariant.flavors + + // Then + assertThat(flavors).isEqualTo(fakeFlavorNames) + } + + @Test + fun `M return mapping file W mappingFile`( + @TempDir fakeMappingDirectory: File, + @StringForgery fakeVariantName: String, + @StringForgery fakeMappingFileName: String + ) { + // Given + val fakeMappingFile = File(fakeMappingDirectory, fakeMappingFileName) + whenever( + mockAndroidVariant.mappingFileProvider + ) doReturn fakeProject.provider { fakeProject.files(fakeMappingFile) } + whenever(mockAndroidVariant.name) doReturn fakeVariantName + + // When + val mappingFile = testedAppVariant.mappingFile + + // Then + assertThat(mappingFile.get().asFile).isEqualTo(fakeMappingFile) + } + + @Test + fun `M return mapping file W mappingFile { fallback to legacy, no mapping files in provider }`( + @StringForgery fakeVariantName: String + ) { + // Given + whenever( + mockAndroidVariant.mappingFileProvider + ) doReturn fakeProject.provider { fakeProject.files() } + whenever(mockAndroidVariant.name) doReturn fakeVariantName + + // When + val mappingFile = testedAppVariant.mappingFile + + // Then + assertThat(mappingFile.get().asFile.path).endsWith( + Paths.get("outputs", "mapping", fakeVariantName, "mapping.txt").toString() + ) + } + + @Test + fun `M return mapping file W mappingFile { fallback to legacy, multiple files in provider }`( + @TempDir fakeMappingDirectory: File, + @StringForgery fakeVariantName: String, + @StringForgery fakeMappingFileNameA: String, + @StringForgery fakeMappingFileNameB: String + ) { + // Given + val fileA = File(fakeMappingDirectory, fakeMappingFileNameA) + val fileB = File(fakeMappingDirectory, fakeMappingFileNameB) + whenever( + mockAndroidVariant.mappingFileProvider + ) doReturn fakeProject.provider { fakeProject.files(fileA, fileB) } + whenever(mockAndroidVariant.name) doReturn fakeVariantName + + // When + val mappingFile = testedAppVariant.mappingFile + + // Then + assertThat(mappingFile.get().asFile.path).endsWith( + Paths.get("outputs", "mapping", fakeVariantName, "mapping.txt").toString() + ) + } + + @Test + fun `M collect java and kotlin source dirs W collectJavaAndKotlinSourceDirectories`( + @TempDir tempDir: File, + forge: Forge + ) { + // Given + val expectedSourceDirectories = mutableListOf() + val mockSourceSets = forge.aList { + val mockSourceProvider = mock() + val fakeJavaDirectories = aList { File(tempDir, anAlphaNumericalString()) } + val fakeKotlinDirectories = aList { File(tempDir, anAlphaNumericalString()) } + expectedSourceDirectories += fakeJavaDirectories + expectedSourceDirectories += fakeKotlinDirectories + whenever(mockSourceProvider.javaDirectories) doReturn fakeJavaDirectories + whenever(mockSourceProvider.kotlinDirectories) doReturn fakeKotlinDirectories + mockSourceProvider + } + whenever(mockAndroidVariant.sourceSets) doReturn mockSourceSets + + // When + val sourceDirectories = testedAppVariant.collectJavaAndKotlinSourceDirectories() + + // Then + assertThat(sourceDirectories.get()) + .isEqualTo(expectedSourceDirectories) + } +} diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/variant/NewApiAppVariantTest.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/variant/NewApiAppVariantTest.kt new file mode 100644 index 00000000..9f19b541 --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/internal/variant/NewApiAppVariantTest.kt @@ -0,0 +1,375 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + +package com.datadog.gradle.plugin.internal.variant + +import com.android.build.api.artifact.Artifacts +import com.android.build.api.artifact.SingleArtifact +import com.android.build.api.variant.ApplicationVariant +import com.android.build.api.variant.ExternalNativeBuild +import com.android.build.api.variant.SourceDirectories +import com.android.build.api.variant.Sources +import com.android.build.api.variant.VariantOutput +import com.datadog.gradle.plugin.utils.forge.Configurator +import fr.xgouchet.elmyr.Forge +import fr.xgouchet.elmyr.annotation.AdvancedForgery +import fr.xgouchet.elmyr.annotation.BoolForgery +import fr.xgouchet.elmyr.annotation.IntForgery +import fr.xgouchet.elmyr.annotation.MapForgery +import fr.xgouchet.elmyr.annotation.StringForgery +import fr.xgouchet.elmyr.annotation.StringForgeryType +import fr.xgouchet.elmyr.junit5.ForgeConfiguration +import fr.xgouchet.elmyr.junit5.ForgeExtension +import org.assertj.core.api.Assertions.assertThat +import org.gradle.api.artifacts.Configuration +import org.gradle.api.file.Directory +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.testfixtures.ProjectBuilder +import org.junit.jupiter.api.BeforeEach +import org.junit.jupiter.api.RepeatedTest +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.ExtendWith +import org.junit.jupiter.api.extension.Extensions +import org.junit.jupiter.api.io.TempDir +import org.mockito.Mock +import org.mockito.Mockito.mock +import org.mockito.junit.jupiter.MockitoExtension +import org.mockito.junit.jupiter.MockitoSettings +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.whenever +import org.mockito.quality.Strictness +import java.io.File +import java.util.Random + +@Extensions( + ExtendWith(ForgeExtension::class), + ExtendWith(MockitoExtension::class) +) +@ForgeConfiguration(value = Configurator::class) +@MockitoSettings(strictness = Strictness.LENIENT) +internal class NewApiAppVariantTest { + + private lateinit var testedAppVariant: NewApiAppVariant + + private val fakeProject = ProjectBuilder.builder().build() + + @Mock + lateinit var mockAndroidVariant: ApplicationVariant + + @BeforeEach + fun `set up`() { + testedAppVariant = NewApiAppVariant( + mockAndroidVariant, + fakeProject + ) + } + + @Test + fun `M return variant name W name`( + @StringForgery fakeName: String + ) { + // Given + whenever(mockAndroidVariant.name) doReturn fakeName + + // When + val name = testedAppVariant.name + + // Then + assertThat(name).isEqualTo(fakeName) + } + + @Test + fun `M return application ID W applicationId`( + @StringForgery fakeApplicationId: String + ) { + // Given + whenever(mockAndroidVariant.applicationId) doReturn fakeApplicationId.asProperty() + + // When + val applicationId = testedAppVariant.applicationId.get() + + // Then + assertThat(applicationId).isEqualTo(fakeApplicationId) + } + + @Test + fun `M return flavor name W flavorName`( + @StringForgery fakeFlavorName: String + ) { + // Given + whenever(mockAndroidVariant.flavorName) doReturn fakeFlavorName + + // When + val flavorName = testedAppVariant.flavorName + + // Then + assertThat(flavorName).isEqualTo(fakeFlavorName) + } + + @Test + fun `M return version name W versionName`( + @IntForgery(min = 1) fakeVersionCode: Int, + @StringForgery fakeVersionName: String, + forge: Forge + ) { + // Given + val validOutput = mock() + whenever(validOutput.enabled) doReturn true.asProperty() + whenever(validOutput.versionCode) doReturn fakeVersionCode.asProperty() + whenever(validOutput.versionName) doReturn fakeVersionName.asProperty() + val fakeNonValidOutputs = forge.aList { + val nonValidOutput = mock() + if (forge.aBool()) { + whenever(nonValidOutput.enabled) doReturn false.asProperty() + whenever(nonValidOutput.versionCode) doReturn fakeVersionCode.asProperty() + } else { + whenever(nonValidOutput.enabled) doReturn true.asProperty() + whenever(nonValidOutput.versionCode) doReturn null.asProperty() + } + nonValidOutput + } + val fakeVariantOutputs = (fakeNonValidOutputs + validOutput).shuffled(Random(forge.seed)) + whenever(mockAndroidVariant.outputs) doReturn fakeVariantOutputs + + // When + val versionName = testedAppVariant.versionName.get() + + // Then + assertThat(versionName).isEqualTo(fakeVersionName) + } + + @Test + fun `M return empty version name W versionName { missing version name }`( + @IntForgery(min = 1) fakeVersionCode: Int, + forge: Forge + ) { + // Given + val validOutput = mock() + whenever(validOutput.enabled) doReturn true.asProperty() + whenever(validOutput.versionCode) doReturn fakeVersionCode.asProperty() + whenever(validOutput.versionName) doReturn null.asProperty() + val fakeNonValidOutputs = forge.aList { + val nonValidOutput = mock() + if (forge.aBool()) { + whenever(nonValidOutput.enabled) doReturn false.asProperty() + whenever(nonValidOutput.versionCode) doReturn fakeVersionCode.asProperty() + } else { + whenever(nonValidOutput.enabled) doReturn true.asProperty() + whenever(nonValidOutput.versionCode) doReturn null.asProperty() + } + nonValidOutput + } + val fakeVariantOutputs = (fakeNonValidOutputs + validOutput).shuffled(Random(forge.seed)) + whenever(mockAndroidVariant.outputs) doReturn fakeVariantOutputs + + // When + val versionName = testedAppVariant.versionName.get() + + // Then + assertThat(versionName).isEmpty() + } + + @Test + fun `M return version code W versionCode`( + @IntForgery(min = 1) fakeVersionCode: Int, + forge: Forge + ) { + val validOutput = mock() + whenever(validOutput.enabled) doReturn true.asProperty() + whenever(validOutput.versionCode) doReturn fakeVersionCode.asProperty() + val fakeNonValidOutputs = forge.aList { + val nonValidOutput = mock() + if (forge.aBool()) { + whenever(nonValidOutput.enabled) doReturn false.asProperty() + whenever(nonValidOutput.versionCode) doReturn fakeVersionCode.asProperty() + } else { + whenever(nonValidOutput.enabled) doReturn true.asProperty() + whenever(nonValidOutput.versionCode) doReturn null.asProperty() + } + nonValidOutput + } + val fakeVariantOutputs = (fakeNonValidOutputs + validOutput).shuffled(Random(forge.seed)) + whenever(mockAndroidVariant.outputs) doReturn fakeVariantOutputs + + // When + val versionCode = testedAppVariant.versionCode.get() + + // Then + assertThat(versionCode).isEqualTo(fakeVersionCode) + } + + @Test + fun `M return default version code W versionCode { no valid variant output }`( + @IntForgery(min = 1) fakeVersionCode: Int, + forge: Forge + ) { + val fakeNonValidOutputs = forge.aList { + val nonValidOutput = mock() + if (forge.aBool()) { + whenever(nonValidOutput.enabled) doReturn false.asProperty() + whenever(nonValidOutput.versionCode) doReturn fakeVersionCode.asProperty() + } else { + whenever(nonValidOutput.enabled) doReturn true.asProperty() + whenever(nonValidOutput.versionCode) doReturn null.asProperty() + } + nonValidOutput + } + whenever(mockAndroidVariant.outputs) doReturn fakeNonValidOutputs + + // When + val versionCode = testedAppVariant.versionCode.get() + + // Then + assertThat(versionCode).isEqualTo(1) + } + + @Test + fun `M return compile configuration W compileConfiguration`() { + // Given + val mockCompileConfiguration = mock() + whenever(mockAndroidVariant.compileConfiguration) doReturn mockCompileConfiguration + + // When + val compileConfiguration = testedAppVariant.compileConfiguration + + // Then + assertThat(compileConfiguration).isSameAs(mockCompileConfiguration) + } + + @Test + fun `M return if native build is enabled W isNativeBuildEnabled`( + @BoolForgery fakeEnabled: Boolean + ) { + // Given + whenever( + mockAndroidVariant.externalNativeBuild + ) doReturn if (fakeEnabled) mock() else null + + // When + val isNativeBuildEnabled = testedAppVariant.isNativeBuildEnabled + + // Then + assertThat(isNativeBuildEnabled).isEqualTo(fakeEnabled) + } + + @Test + fun `M return if minification is enabled W isMinifyEnabled`( + @BoolForgery fakeMinifyEnabled: Boolean + ) { + // Given + @Suppress("UnstableApiUsage") + whenever(mockAndroidVariant.isMinifyEnabled) doReturn fakeMinifyEnabled + + // When + val isMinifyEnabled = testedAppVariant.isMinifyEnabled + + // Then + assertThat(isMinifyEnabled).isEqualTo(fakeMinifyEnabled) + } + + @Test + fun `M return build type name W buildTypeName`( + @StringForgery fakeBuildTypeName: String + ) { + // Given + whenever(mockAndroidVariant.buildType) doReturn fakeBuildTypeName + + // When + val buildTypeName = testedAppVariant.buildTypeName + + // Then + assertThat(buildTypeName).isEqualTo(fakeBuildTypeName) + } + + @Test + fun `M return flavor names W flavors`( + @MapForgery( + key = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)]), + value = AdvancedForgery(string = [StringForgery(StringForgeryType.ALPHABETICAL)]) + ) fakeFlavors: Map + ) { + // Given + whenever(mockAndroidVariant.productFlavors) doReturn fakeFlavors.toList() + + // When + val flavors = testedAppVariant.flavors + + // Then + assertThat(flavors).isEqualTo(fakeFlavors.values.toList()) + } + + @Test + fun `M return mapping file W mappingFile`( + @StringForgery fakeMappingFileName: String + ) { + // Given + val fakeMappingFile = File(fakeProject.layout.buildDirectory.get().asFile, fakeMappingFileName) + val mockArtifacts = mock() + whenever( + mockArtifacts.get(SingleArtifact.OBFUSCATION_MAPPING_FILE) + ) doReturn fakeProject.layout.buildDirectory.file(fakeMappingFile.path) + whenever(mockAndroidVariant.artifacts) doReturn mockArtifacts + + // When + val mappingFile = testedAppVariant.mappingFile + + // Then + assertThat(mappingFile.get().asFile).isEqualTo(fakeMappingFile) + } + + @RepeatedTest(4) + fun `M collect java and kotlin source dirs W collectJavaAndKotlinSourceDirectories`( + @TempDir tempDir: File, + @BoolForgery includeJava: Boolean, + @BoolForgery includeKotlin: Boolean, + forge: Forge + ) { + // Given + val expectedSourceDirectories = mutableListOf() + val mockSources = mock() + if (includeJava) { + val mockJavaSources = mock() + val fakeJavaDirectories = forge.aList { File(tempDir, anAlphaNumericalString()) } + whenever( + mockJavaSources.all + ) doReturn fakeJavaDirectories.map { it.asDirectory() }.asListProperty() + whenever(mockSources.java) doReturn mockJavaSources + expectedSourceDirectories += fakeJavaDirectories + } + if (includeKotlin) { + val mockKotlinSources = mock() + val fakeKotlinDirectories = forge.aList { File(tempDir, anAlphaNumericalString()) } + whenever( + mockKotlinSources.all + ) doReturn fakeKotlinDirectories.map { it.asDirectory() }.asListProperty() + @Suppress("UnstableApiUsage") + whenever(mockSources.kotlin) doReturn mockKotlinSources + expectedSourceDirectories += fakeKotlinDirectories + } + whenever(mockAndroidVariant.sources) doReturn mockSources + + // When + val sourceDirectories = testedAppVariant.collectJavaAndKotlinSourceDirectories() + + // Then + assertThat(sourceDirectories.get()) + .isEqualTo(expectedSourceDirectories) + } + + // region private + + private inline fun T.asProperty(): Property = + fakeProject.objects.property(T::class.java).value(this) + + private inline fun Collection.asListProperty(): ListProperty = + fakeProject.objects.listProperty(T::class.java).value(this) + + private fun File.asDirectory(): Directory = + fakeProject.objects.directoryProperty().fileValue(this).get() + + // endregion +} diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/BuildResultAssert.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/BuildResultAssert.kt index fe77d072..875e8117 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/BuildResultAssert.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/BuildResultAssert.kt @@ -1,7 +1,13 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2020-Present Datadog, Inc. + */ + package com.datadog.gradle.plugin.utils.assertj import com.datadog.gradle.plugin.DdAndroidGradlePlugin -import com.datadog.gradle.plugin.DdNdkSymbolFileUploadTask +import com.datadog.gradle.plugin.NdkSymbolFileUploadTask import org.assertj.core.api.AbstractAssert import org.assertj.core.api.Assertions.assertThat import org.gradle.testkit.runner.BuildResult @@ -39,14 +45,14 @@ internal class BuildResultAssert(actual: BuildResult) : it.path.contains(DdAndroidGradlePlugin.UPLOAD_TASK_NAME) } assertThat(actual.tasks).noneMatch { - it.path.contains(DdNdkSymbolFileUploadTask.TASK_NAME) + it.path.contains(NdkSymbolFileUploadTask.TASK_NAME) } return this } fun hasNoNdkSymbolUploadTasks(): BuildResultAssert { assertThat(actual.tasks).noneMatch { - it.path.contains(DdNdkSymbolFileUploadTask.TASK_NAME) + it.path.contains(NdkSymbolFileUploadTask.TASK_NAME) } return this } diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RecordedRequestAssert.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/RecordedRequestAssert.kt similarity index 98% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RecordedRequestAssert.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/RecordedRequestAssert.kt index be23dcbe..ed18dd66 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RecordedRequestAssert.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/assertj/RecordedRequestAssert.kt @@ -4,7 +4,7 @@ * Copyright 2020-Present Datadog, Inc. */ -package com.datadog.gradle.plugin +package com.datadog.gradle.plugin.utils.assertj import okhttp3.mockwebserver.RecordedRequest import okio.GzipSource diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/Configurator.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/Configurator.kt similarity index 94% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/Configurator.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/Configurator.kt index 2e7549f5..8420c91b 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/Configurator.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/Configurator.kt @@ -4,7 +4,7 @@ * Copyright 2020-Present Datadog, Inc. */ -package com.datadog.gradle.plugin +package com.datadog.gradle.plugin.utils.forge import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeConfigurator diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdExtensionConfigurationForgeryFactory.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/DdExtensionConfigurationForgeryFactory.kt similarity index 88% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdExtensionConfigurationForgeryFactory.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/DdExtensionConfigurationForgeryFactory.kt index 804d18cf..f6057182 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdExtensionConfigurationForgeryFactory.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/DdExtensionConfigurationForgeryFactory.kt @@ -4,8 +4,11 @@ * Copyright 2020-Present Datadog, Inc. */ -package com.datadog.gradle.plugin +package com.datadog.gradle.plugin.utils.forge +import com.datadog.gradle.plugin.DatadogSite +import com.datadog.gradle.plugin.DdExtensionConfiguration +import com.datadog.gradle.plugin.SdkCheckLevel import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdExtensionForgeryFactory.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/DdExtensionForgeryFactory.kt similarity index 90% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdExtensionForgeryFactory.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/DdExtensionForgeryFactory.kt index ff54cfb0..b4e9b318 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/DdExtensionForgeryFactory.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/DdExtensionForgeryFactory.kt @@ -4,8 +4,11 @@ * Copyright 2020-Present Datadog, Inc. */ -package com.datadog.gradle.plugin +package com.datadog.gradle.plugin.utils.forge +import com.datadog.gradle.plugin.DatadogSite +import com.datadog.gradle.plugin.DdExtension +import com.datadog.gradle.plugin.SdkCheckLevel import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory import org.gradle.api.model.ObjectFactory diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/IdentifierForgeryFactory.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/IdentifierForgeryFactory.kt similarity index 95% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/IdentifierForgeryFactory.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/IdentifierForgeryFactory.kt index 2cd388cb..2b185caf 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/IdentifierForgeryFactory.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/IdentifierForgeryFactory.kt @@ -4,7 +4,7 @@ * Copyright 2020-Present Datadog, Inc. */ -package com.datadog.gradle.plugin +package com.datadog.gradle.plugin.utils.forge import com.datadog.gradle.plugin.internal.DdAppIdentifier import fr.xgouchet.elmyr.Forge diff --git a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoForgeryFactory.kt b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/RepositoryInfoForgeryFactory.kt similarity index 89% rename from dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoForgeryFactory.kt rename to dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/RepositoryInfoForgeryFactory.kt index 26f445f9..c90710ba 100644 --- a/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/RepositoryInfoForgeryFactory.kt +++ b/dd-sdk-android-gradle-plugin/src/test/kotlin/com/datadog/gradle/plugin/utils/forge/RepositoryInfoForgeryFactory.kt @@ -4,8 +4,9 @@ * Copyright 2020-Present Datadog, Inc. */ -package com.datadog.gradle.plugin +package com.datadog.gradle.plugin.utils.forge +import com.datadog.gradle.plugin.RepositoryInfo import fr.xgouchet.elmyr.Case import fr.xgouchet.elmyr.Forge import fr.xgouchet.elmyr.ForgeryFactory diff --git a/dd-sdk-android-gradle-plugin/src/test/resources/build_with_android_components.gradle b/dd-sdk-android-gradle-plugin/src/test/resources/build_with_android_components.gradle new file mode 100644 index 00000000..e93685ec --- /dev/null +++ b/dd-sdk-android-gradle-plugin/src/test/resources/build_with_android_components.gradle @@ -0,0 +1,58 @@ +plugins { + id("com.android.application") + id("kotlin-android") + id("com.datadoghq.dd-sdk-android-gradle-plugin") +} + +repositories { + google() + mavenCentral() +} + +extensions.configure("androidComponents") { extension -> + extension.onVariants(extension.selector().all()) { variant -> + // just request assets, this will be enough to reproduce + // https://issuetracker.google.com/issues/342428022 when using new Variant API + legacy one + // with AGP 8.4.0 + if (variant.metaClass.respondsTo(variant, "getSources")) { + // available only since AGP 7.2.0 + variant.getSources().getAssets() + } + } +} + +android { + compileSdkVersion = rootProject.ext.targetSdkVersion + buildToolsVersion = rootProject.ext.buildToolsVersion + + defaultConfig { + applicationId "com.example.variants" + minSdkVersion 21 + targetSdkVersion rootProject.ext.targetSdkVersion + versionCode 1 + versionName "1.0" + multiDexEnabled = true + } + + buildTypes { + release { + minifyEnabled true + proguardFiles getDefaultProguardFile ('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + namespace = "com.example.variants" + + compileOptions { + sourceCompatibility = rootProject.ext.jvmTarget + targetCompatibility = rootProject.ext.jvmTarget + } + + kotlinOptions { + jvmTarget = rootProject.ext.jvmTarget + } +} + +dependencies { + implementation(rootProject.ext.datadogSdkDependency) +} diff --git a/detekt.yml b/detekt.yml deleted file mode 100644 index 031492ea..00000000 --- a/detekt.yml +++ /dev/null @@ -1,570 +0,0 @@ -build: - maxIssues: 0 - weights: - # complexity: 2 - # LongParameterList: 1 - # style: 1 - # comments: 1 - -processors: - active: true - exclude: - # - 'DetektProgressListener' - # - 'FunctionCountProcessor' - # - 'PropertyCountProcessor' - # - 'ClassCountProcessor' - # - 'PackageCountProcessor' - # - 'KtFileCountProcessor' - -console-reports: - active: true - exclude: - # - 'ProjectStatisticsReport' - # - 'ComplexityReport' - # - 'NotificationReport' - # - 'FindingsReport' - - 'FileBasedFindingsReport' - # - 'BuildFailureReport' - -comments: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - CommentOverPrivateFunction: - active: true - CommentOverPrivateProperty: - active: true - EndOfSentenceFormat: - active: true - endOfSentenceFormat: ([.?!][ \t\n\r\f<])|([.?!:]$) - UndocumentedPublicClass: - active: true - searchInNestedClass: true - searchInInnerClass: true - searchInInnerObject: true - searchInInnerInterface: true - UndocumentedPublicFunction: - active: true - UndocumentedPublicProperty: - active: true - -complexity: - active: true - ComplexCondition: - active: true - threshold: 4 - ComplexInterface: - active: true - threshold: 10 - includeStaticDeclarations: false - ComplexMethod: - active: true - threshold: 10 - ignoreSingleWhenExpression: true - ignoreSimpleWhenEntries: true - LabeledExpression: - active: true - ignoredLabels: "" - LargeClass: - active: true - threshold: 600 - LongMethod: - active: true - threshold: 60 - LongParameterList: - active: true - threshold: 6 - ignoreDefaultParameters: true - MethodOverloading: - active: true - threshold: 6 - NestedBlockDepth: - active: true - threshold: 4 - StringLiteralDuplication: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - threshold: 3 - ignoreAnnotation: true - excludeStringsWithLessThan5Characters: true - ignoreStringsRegex: '$^' - TooManyFunctions: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - thresholdInFiles: 11 - thresholdInClasses: 11 - thresholdInInterfaces: 11 - thresholdInObjects: 11 - thresholdInEnums: 11 - ignoreDeprecated: true - ignorePrivate: true - ignoreOverridden: true - -empty-blocks: - active: true - EmptyCatchBlock: - active: true - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" - EmptyClassBlock: - active: true - EmptyDefaultConstructor: - active: true - EmptyDoWhileBlock: - active: true - EmptyElseBlock: - active: true - EmptyFinallyBlock: - active: true - EmptyForBlock: - active: true - EmptyFunctionBlock: - active: true - ignoreOverridden: true - EmptyIfBlock: - active: true - EmptyInitBlock: - active: true - EmptyKtFile: - active: true - EmptySecondaryConstructor: - active: true - EmptyWhenBlock: - active: true - EmptyWhileBlock: - active: true - -exceptions: - active: true - ExceptionRaisedInUnexpectedLocation: - active: true - methodNames: 'toString,hashCode,equals,finalize' - InstanceOfCheckForException: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - NotImplementedDeclaration: - active: true - PrintStackTrace: - active: true - RethrowCaughtException: - active: true - ReturnFromFinally: - active: true - ignoreLabeled: true - SwallowedException: - active: true - ignoredExceptionTypes: 'InterruptedException,NumberFormatException,ParseException,MalformedURLException' - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" - ThrowingExceptionFromFinally: - active: true - ThrowingExceptionInMain: - active: true - ThrowingExceptionsWithoutMessageOrCause: - active: true - exceptions: 'IllegalArgumentException,IllegalStateException,IOException' - ThrowingNewInstanceOfSameException: - active: true - TooGenericExceptionCaught: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - exceptionNames: - - ArrayIndexOutOfBoundsException - - Error - - Exception - - IllegalMonitorStateException - - NullPointerException - - IndexOutOfBoundsException - - RuntimeException - - Throwable - allowedExceptionNameRegex: "^(_|(ignore|expected).*)" - TooGenericExceptionThrown: - active: true - exceptionNames: - - Error - - Exception - - Throwable - - RuntimeException - -formatting: - active: false - android: false - autoCorrect: true - AnnotationOnSeparateLine: - active: true - autoCorrect: true - ChainWrapping: - active: true - autoCorrect: true - CommentSpacing: - active: true - autoCorrect: true - Filename: - active: true - FinalNewline: - active: true - autoCorrect: true - ImportOrdering: - active: true - autoCorrect: true - Indentation: - active: true - autoCorrect: true - indentSize: 4 - continuationIndentSize: 4 - MaximumLineLength: - active: true - maxLineLength: 120 - ModifierOrdering: - active: true - autoCorrect: true - MultiLineIfElse: - active: true - autoCorrect: true - NoBlankLineBeforeRbrace: - active: true - autoCorrect: true - NoConsecutiveBlankLines: - active: true - autoCorrect: true - NoEmptyClassBody: - active: true - autoCorrect: true - NoLineBreakAfterElse: - active: true - autoCorrect: true - NoLineBreakBeforeAssignment: - active: true - autoCorrect: true - NoMultipleSpaces: - active: true - autoCorrect: true - NoSemicolons: - active: true - autoCorrect: true - NoTrailingSpaces: - active: true - autoCorrect: true - NoUnitReturn: - active: true - autoCorrect: true - NoUnusedImports: - active: true - autoCorrect: true - NoWildcardImports: - active: true - autoCorrect: true - PackageName: - active: true - autoCorrect: true - ParameterListWrapping: - active: true - autoCorrect: true - indentSize: 4 - SpacingAroundColon: - active: true - autoCorrect: true - SpacingAroundComma: - active: true - autoCorrect: true - SpacingAroundCurly: - active: true - autoCorrect: true - SpacingAroundDot: - active: true - autoCorrect: true - SpacingAroundKeyword: - active: true - autoCorrect: true - SpacingAroundOperators: - active: true - autoCorrect: true - SpacingAroundParens: - active: true - autoCorrect: true - SpacingAroundRangeOperator: - active: true - autoCorrect: true - StringTemplate: - active: true - autoCorrect: true - -naming: - active: true - ClassNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - classPattern: '[A-Z$][a-zA-Z0-9$]*' - ConstructorParameterNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - parameterPattern: '[a-z][A-Za-z0-9]*' - privateParameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - EnumNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - enumEntryPattern: '^[A-Z][_a-zA-Z0-9]*' - ForbiddenClassName: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - forbiddenName: '' - FunctionMaxLength: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - maximumFunctionNameLength: 30 - FunctionMinLength: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - minimumFunctionNameLength: 3 - FunctionNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - functionPattern: '^([a-z$][a-zA-Z$0-9]*)|(`.*`)$' - excludeClassPattern: '$^' - ignoreOverridden: true - FunctionParameterNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - parameterPattern: '[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - InvalidPackageDeclaration: - active: true - rootPackage: '' - MatchingDeclarationName: - active: true - MemberNameEqualsClassName: - active: true - ignoreOverridden: true - ObjectPropertyNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - constantPattern: '[A-Za-z][_A-Za-z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '(_)?[A-Za-z][_A-Za-z0-9]*' - PackageNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - packagePattern: '^[a-z]+(\.[a-z][A-Za-z0-9]*)*$' - TopLevelPropertyNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - constantPattern: '[A-Z][_A-Z0-9]*' - propertyPattern: '[A-Za-z][_A-Za-z0-9]*' - privatePropertyPattern: '_?[A-Za-z][_A-Za-z0-9]*' - VariableMaxLength: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - maximumVariableNameLength: 64 - VariableMinLength: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - minimumVariableNameLength: 1 - VariableNaming: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - variablePattern: '[a-z][A-Za-z0-9]*' - privateVariablePattern: '(_)?[a-z][A-Za-z0-9]*' - excludeClassPattern: '$^' - ignoreOverridden: true - -performance: - active: true - ArrayPrimitive: - active: true - ForEachOnRange: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - SpreadOperator: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - UnnecessaryTemporaryInstantiation: - active: true - -potential-bugs: - active: true - Deprecation: - active: true - DuplicateCaseInWhenExpression: - active: true - EqualsAlwaysReturnsTrueOrFalse: - active: true - EqualsWithHashCodeExist: - active: true - ExplicitGarbageCollectionCall: - active: true - HasPlatformType: - active: true - InvalidRange: - active: true - IteratorHasNextCallsNextMethod: - active: true - IteratorNotThrowingNoSuchElementException: - active: true - LateinitUsage: - active: false - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - excludeAnnotatedProperties: "" - ignoreOnClassesPattern: "" - MissingWhenCase: - active: true - RedundantElseInWhen: - active: true - UnconditionalJumpStatementInLoop: - active: true - UnreachableCode: - active: true - UnsafeCallOnNullableType: - active: true - UnsafeCast: - active: true - UselessPostfixExpression: - active: true - WrongEqualsTypeParameter: - active: true - -style: - active: false - CollapsibleIfStatements: - active: true - DataClassContainsFunctions: - active: true - conversionFunctionPrefix: 'to' - DataClassShouldBeImmutable: - active: true - EqualsNullCall: - active: true - EqualsOnSignatureLine: - active: true - ExplicitItLambdaParameter: - active: true - ExpressionBodySyntax: - active: false - includeLineWrapping: false - ForbiddenComment: - active: true - values: 'TODO:,FIXME:,STOPSHIP:' - allowedPatterns: "" - ForbiddenImport: - active: true - imports: '' - forbiddenPatterns: "" - ForbiddenVoid: - active: true - ignoreOverridden: false - ignoreUsageInGenerics: true - FunctionOnlyReturningConstant: - active: true - ignoreOverridableFunction: true - excludedFunctions: 'describeContents' - excludeAnnotatedFunction: "dagger.Provides" - LibraryCodeMustSpecifyReturnType: - active: true - LoopWithTooManyJumpStatements: - active: true - maxJumpCount: 1 - MagicNumber: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - ignoreNumbers: '-1,0,1,2' - ignoreHashCodeFunction: true - ignorePropertyDeclaration: false - ignoreConstantDeclaration: true - ignoreCompanionObjectPropertyDeclaration: true - ignoreAnnotation: false - ignoreNamedArgument: true - ignoreEnums: false - ignoreRanges: false - MandatoryBracesIfStatements: - active: true - MaxLineLength: - active: true - maxLineLength: 120 - excludePackageStatements: true - excludeImportStatements: true - excludeCommentStatements: false - MayBeConst: - active: true - ModifierOrder: - active: true - NestedClassesVisibility: - active: true - NewLineAtEndOfFile: - active: false - NoTabs: - active: true - OptionalAbstractKeyword: - active: true - OptionalUnit: - active: true - OptionalWhenBraces: - active: true - PreferToOverPairSyntax: - active: true - ProtectedMemberInFinalClass: - active: true - RedundantExplicitType: - active: true - RedundantVisibilityModifierRule: - active: true - ReturnCount: - active: true - max: 2 - excludedFunctions: "equals" - excludeLabeled: false - excludeReturnFromLambda: true - excludeGuardClauses: true - SafeCast: - active: true - SerialVersionUIDInSerializableClass: - active: true - SpacingBetweenPackageAndImports: - active: true - ThrowsCount: - active: true - max: 2 - TrailingWhitespace: - active: true - UnderscoresInNumericLiterals: - active: true - acceptableDecimalLength: 5 - UnnecessaryAbstractClass: - active: true - excludeAnnotatedClasses: "dagger.Module" - UnnecessaryApply: - active: true - UnnecessaryInheritance: - active: true - UnnecessaryLet: - active: true - UnnecessaryParentheses: - active: true - UntilInsteadOfRangeTo: - active: true - UnusedImports: - active: true - UnusedPrivateClass: - active: true - UnusedPrivateMember: - active: true - allowedNames: "(_|ignored|expected|serialVersionUID)" - UseArrayLiteralsInAnnotations: - active: true - UseCheckOrError: - active: true - UseDataClass: - active: true - excludeAnnotatedClasses: "" - allowVars: true - UseIfInsteadOfWhen: - active: true - UseRequire: - active: true - UselessCallOnNotNull: - active: true - UtilityClassWithPublicConstructor: - active: true - VarCouldBeVal: - active: true - WildcardImport: - active: true - excludes: "**/test/**,**/androidTest/**,**/*.Test.kt,**/*.Spec.kt,**/*.Spek.kt" - excludeImports: 'java.util.*,kotlinx.android.synthetic.*' diff --git a/dogfood.py b/dogfood.py new file mode 100755 index 00000000..6de6494e --- /dev/null +++ b/dogfood.py @@ -0,0 +1,155 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019-Present Datadog, Inc + +import re +import subprocess +import sys +import os +from argparse import ArgumentParser, Namespace +from tempfile import TemporaryDirectory +from typing import Tuple +from xmlrpc.client import Boolean + +import requests +from git import Repo + +TARGET_APP = "app" +TARGET_DEMO = "demo" +TARGET_GRADLE_PLUGIN = "gradle-plugin" + +REPOSITORIES = { + TARGET_APP: "datadog-android", + TARGET_DEMO: "shopist-android", + TARGET_GRADLE_PLUGIN: "dd-sdk-android-gradle-plugin", + # Flutter is not needed because it pulls updates instead of being pushed them with the dogfood script. +} + +FILE_PATH = { + TARGET_APP: os.path.join("gradle", "libs.versions.toml"), + TARGET_DEMO: os.path.join("gradle", "libs.versions.toml"), + TARGET_GRADLE_PLUGIN: os.path.join("gradle", "libs.versions.toml"), +} + +PREFIX = { + TARGET_APP: "datadog-plugin", + TARGET_DEMO: "datadogPluginGradle", + TARGET_GRADLE_PLUGIN: "datadogPluginGradle", +} + + +def parse_arguments(args: list) -> Namespace: + parser = ArgumentParser() + + parser.add_argument("-v", "--version", required=True, help="the version of the Datadog Gradle Plugin") + parser.add_argument("-t", "--target", required=True, + choices=[TARGET_APP, TARGET_DEMO, TARGET_GRADLE_PLUGIN], + help="the target repository") + parser.add_argument("-d", "--dry-run", required=False, dest="dry_run", + help="Don't push changes or create a PR.", action="store_true") + + return parser.parse_args(args) + + +def github_create_pr(repository: str, branch_name: str, base_name: str, version: str, previous_version: str, gh_token: str) -> int: + headers = { + 'authorization': "Bearer " + gh_token, + 'Accept': 'application/vnd.github.v3+json', + } + body = "This PR has been created automatically by the CI" + if previous_version: + diff = "Updating Datadog Gradle Plugin from version {previous_version} to version {version}: [diff](https://github.com/DataDog/dd-sdk-android-gradle-plugin/compare/{previous_version}...{version})".format(previous_version=previous_version, version=version) + body = "\\n".join([body, diff]) + data = '{"body": "' + body + '", ' \ + '"title": "Update Datadog Gradle Plugin to version ' + version + '", ' \ + '"base":"' + base_name + '", "head":"' + branch_name + '"}' + + url = "https://api.github.com/repos/DataDog/" + repository + "/pulls" + response = requests.post(url=url, headers=headers, data=data) + if response.status_code == 201: + print("Pull Request created successfully") + return 0 + else: + print("pull request failed " + str(response.status_code) + '\n' + response.text) + return 1 + +def generate_target_code(target: str, temp_dir_path: str, version: str): + print("Generating code with version " + version) + file_path = FILE_PATH[target] + target_file = os.path.join(temp_dir_path, file_path) + prefix = PREFIX[target] + regex = prefix + " = \"[0-9a-z\\.-]+\"" + + previous_version = None + + with open(target_file, 'r') as target: + content = target.read() + previous_version_search = re.search(prefix + " = \"(.*)\"", content, flags=re.M) + if previous_version_search: + previous_version = previous_version_search.group(1) + updated_content = re.sub(regex, prefix + " = \"" + version + "\"", content, flags=re.M) + + with open(target_file, 'w') as target: + target.write(updated_content) + + return previous_version + + +def git_clone_repository(repo_name: str, gh_token: str, temp_dir_path: str) -> Tuple[Repo, str]: + print("Cloning repository " + repo_name) + url = "https://" + gh_token + ":x-oauth-basic@github.com/DataDog/" + repo_name + repo = Repo.clone_from(url, temp_dir_path) + base_name = repo.active_branch.name + return repo, base_name + + +def git_push_changes(repo: Repo, version: str): + print("Committing changes") + repo.git.add(update=True) + repo.index.commit("Update Datadog Gradle Plugin to " + version) + + print("Pushing branch") + origin = repo.remote(name="origin") + repo.git.push("--set-upstream", "--force", origin, repo.head.ref) + + +def update_dependant(version: str, target: str, gh_token: str, dry_run: bool) -> int: + branch_name = "update_datadog_gradle_plugin_" + version + temp_dir = TemporaryDirectory() + temp_dir_path = temp_dir.name + repo_name = REPOSITORIES[target] + + repo, base_name = git_clone_repository(repo_name, gh_token, temp_dir_path) + + print("Creating branch " + branch_name) + repo.git.checkout('HEAD', b=branch_name) + + previous_version = generate_target_code(target, temp_dir_path, version) + + if not repo.is_dirty(): + print("Nothing to commit, all is in order-") + return 0 + + if not dry_run: + git_push_changes(repo, version) + + return github_create_pr(repo_name, branch_name, base_name, version, previous_version, gh_token) + + return 0 + +def run_main() -> int: + cli_args = parse_arguments(sys.argv[1:]) + + # This script expects to have a valid Github Token in a "gh_token" text file + # The token needs the `repo` permissions, and for now is a PAT + with open('gh_token', 'r') as f: + gh_token = f.read().strip() + + return update_dependant(cli_args.version, cli_args.target, gh_token, cli_args.dry_run) + + +if __name__ == "__main__": + sys.exit(run_main()) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7fda21cf..27ec12e4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ json = "20180813" okHttp = "4.12.0" # Android -androidToolsPlugin = "8.3.1" +androidToolsPlugin = "8.4.1" # AndroidX androidx-core ="1.3.2" @@ -27,7 +27,6 @@ elmyr = "1.3.1" mockitoKotlin = "5.0.0" # Tools -detekt = "1.17.0" dokka = "1.9.20" unmock = "0.7.5" @@ -38,8 +37,8 @@ versionsPluginGradle = "0.33.0" kotlinGrammarParser = "c35b50fa44" # Datadog -datadogSdk = "2.7.1" -datadogPluginGradle = "1.12.0" +datadogSdk = "2.10.0" +datadogPluginGradle = "1.13.1" [libraries] @@ -49,7 +48,6 @@ androidToolsPluginGradle = { module = "com.android.tools.build:gradle", version. kotlinPluginGradle = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } dokkaPluginGradle = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokka" } unmockPluginGradle = { module = "de.mobilej.unmock:UnMockPlugin", version.ref = "unmock" } -detektPluginGradle = { module = "io.gitlab.arturbosch.detekt:detekt-gradle-plugin", version.ref = "detekt" } versionsPluginGradle = { module = "com.github.ben-manes:gradle-versions-plugin", version.ref = "versionsPluginGradle" } @@ -87,7 +85,6 @@ mockitoKotlin = { module = "org.mockito.kotlin:mockito-kotlin", version.ref = "m kotlinReflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } # Tools -detektCli = { module = "io.gitlab.arturbosch.detekt:detekt-cli", version.ref = "detekt" } okHttpMock = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "okHttp" } fuzzyWuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzyWuzzy" } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2ea3535d..e7646dea 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/libs/dd-java-agent-0.98.1.jar b/libs/dd-java-agent-1.26.1.jar similarity index 59% rename from libs/dd-java-agent-0.98.1.jar rename to libs/dd-java-agent-1.26.1.jar index e09ce9f1..1fd391b0 100644 Binary files a/libs/dd-java-agent-0.98.1.jar and b/libs/dd-java-agent-1.26.1.jar differ diff --git a/samples/basic/src/main/AndroidManifest.xml b/samples/basic/src/main/AndroidManifest.xml index 61ce2fbc..76549b15 100644 --- a/samples/basic/src/main/AndroidManifest.xml +++ b/samples/basic/src/main/AndroidManifest.xml @@ -5,8 +5,7 @@ ~ Copyright 2020-Present Datadog, Inc. --> - + - + - +