From 1a78899677887574e8ad86ea010788c777472f4b Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Thu, 29 Sep 2022 18:07:10 +0300 Subject: [PATCH 01/10] Issue#35 Update Detekt reports --- build.gradle | 5 +---- gradlescripts/detekt.config.gradle | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 8318014..1e53ada 100644 --- a/build.gradle +++ b/build.gradle @@ -15,13 +15,10 @@ buildscript { classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" + classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version" } } -plugins { - id "io.gitlab.arturbosch.detekt" version "$detekt_version" -} - allprojects { repositories { mavenCentral() diff --git a/gradlescripts/detekt.config.gradle b/gradlescripts/detekt.config.gradle index 2214998..9572e2c 100644 --- a/gradlescripts/detekt.config.gradle +++ b/gradlescripts/detekt.config.gradle @@ -1,3 +1,4 @@ +apply plugin: "io.gitlab.arturbosch.detekt" detekt { toolVersion = "$detekt_version" @@ -16,14 +17,15 @@ detekt { source = files(*projectPaths) config = files("$projectDir/detekt/detekt.yml") baseline = file("$projectDir/detekt/baseline.xml") +} + +tasks.getByName("detekt") { reports { - txt { - enabled = true - destination = file("build/reports/detekt.txt") - } - html { - enabled = true - destination = file("build/reports/detekt.html") - } + txt.required.set(true) + txt.outputLocation.set(file("build/reports/detekt.txt")) + html.required.set(true) + html.outputLocation.set(file("build/reports/detekt.html")) + xml.required.set(false) + sarif.required.set(false) } } \ No newline at end of file From a5dcdd34092fae43c5507895b50a08d92fce8278 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Fri, 30 Sep 2022 21:17:41 +0300 Subject: [PATCH 02/10] Issue#35 version updates --- app-shared-test/build.gradle | 6 +++--- app/build.gradle | 8 ++++---- build.gradle | 14 +++++++++----- .../example-navcontroller-shared-test/build.gradle | 6 +++--- examplecase/example-navcontroller/build.gradle | 6 +++--- gradle/wrapper/gradle-wrapper.properties | 4 ++-- gradlescripts/versions.gradle | 4 ++-- hilt/hilt-app-shared-test/build.gradle | 6 +++--- hilt/hilt-app/build.gradle | 8 ++++---- hilt/hilt-network-di-test-util/build.gradle | 6 +++--- test-util-android/build.gradle | 6 +++--- test-util-junit5-android/build.gradle | 6 +++--- test-util-shared-android/build.gradle | 6 +++--- test-util-shared-robolectric/build.gradle | 6 +++--- 14 files changed, 48 insertions(+), 44 deletions(-) diff --git a/app-shared-test/build.gradle b/app-shared-test/build.gradle index deeb0dc..73c77be 100644 --- a/app-shared-test/build.gradle +++ b/app-shared-test/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/app/build.gradle b/app/build.gradle index d18fe4a..4d12962 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -5,12 +5,12 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { applicationId "org.fnives.test.showcase" - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" buildConfigField "String", "BASE_URL", '"https://606844a10add49001733fe6b.mockapi.io/"' @@ -37,7 +37,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = project.androidx_compose_version + kotlinCompilerExtensionVersion = project.compose_compiler_version } sourceSets { diff --git a/build.gradle b/build.gradle index 1e53ada..0644343 100644 --- a/build.gradle +++ b/build.gradle @@ -1,20 +1,24 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = "1.6.10" + ext.kotlin_version = "1.7.10" ext.detekt_version = "1.19.0" ext.navigation_version = "2.4.2" - ext.hilt_version = "2.40.5" + ext.hilt_version = "2.44" + ext.compose_compiler_version = "1.3.1" + ext.compileSdkVersion = 32 + ext.minSdkVersion = 21 + ext.targetSdkVersion = 32 repositories { mavenCentral() google() maven { url "https://plugins.gradle.org/m2/" } } dependencies { - classpath 'com.android.tools.build:gradle:7.1.3' + classpath 'com.android.tools.build:gradle:7.3.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navigation_version" classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" + classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version" } } @@ -45,4 +49,4 @@ apply from: 'gradlescripts/lint.gradle' apply from: 'gradlescripts/testoptions.gradle' apply from: 'gradlescripts/test.tasks.gradle' apply from: 'gradlescripts/testdependencies.gradle' -apply from: 'gradlescripts/disable.test.task.gradle' \ No newline at end of file +apply from: 'gradlescripts/disable.test.task.gradle' diff --git a/examplecase/example-navcontroller-shared-test/build.gradle b/examplecase/example-navcontroller-shared-test/build.gradle index a33e5ad..e713850 100644 --- a/examplecase/example-navcontroller-shared-test/build.gradle +++ b/examplecase/example-navcontroller-shared-test/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/examplecase/example-navcontroller/build.gradle b/examplecase/example-navcontroller/build.gradle index 323fbee..a4a4c24 100644 --- a/examplecase/example-navcontroller/build.gradle +++ b/examplecase/example-navcontroller/build.gradle @@ -5,11 +5,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 804c205..9ea2eaa 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Jan 27 21:44:07 EET 2022 +#Fri Sep 30 19:34:26 EEST 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME diff --git a/gradlescripts/versions.gradle b/gradlescripts/versions.gradle index b393ad4..fd6d825 100644 --- a/gradlescripts/versions.gradle +++ b/gradlescripts/versions.gradle @@ -7,9 +7,9 @@ project.ext { androidx_swiperefreshlayout_version = "1.1.0" room_version = "2.4.3" activity_ktx_version = "1.4.0" - androidx_navigation = "2.4.0" + androidx_navigation = "2.5.1" - androidx_compose_version = "1.1.0" + androidx_compose_version = "1.2.1" google_accompanist_version = "0.23.1" androidx_compose_constraintlayout_version = "1.0.0" diff --git a/hilt/hilt-app-shared-test/build.gradle b/hilt/hilt-app-shared-test/build.gradle index 740e553..a7f894d 100644 --- a/hilt/hilt-app-shared-test/build.gradle +++ b/hilt/hilt-app-shared-test/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/hilt/hilt-app/build.gradle b/hilt/hilt-app/build.gradle index 1dd1fd2..a2c713a 100644 --- a/hilt/hilt-app/build.gradle +++ b/hilt/hilt-app/build.gradle @@ -6,12 +6,12 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { applicationId "org.fnives.test.showcase.hilt" - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" buildConfigField "String", "BASE_URL", '"https://606844a10add49001733fe6b.mockapi.io/"' @@ -38,7 +38,7 @@ android { compose = true } composeOptions { - kotlinCompilerExtensionVersion = project.androidx_compose_version + kotlinCompilerExtensionVersion = project.compose_compiler_version } sourceSets { diff --git a/hilt/hilt-network-di-test-util/build.gradle b/hilt/hilt-network-di-test-util/build.gradle index 55921d0..fca3641 100644 --- a/hilt/hilt-network-di-test-util/build.gradle +++ b/hilt/hilt-network-di-test-util/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion consumerProguardFiles "consumer-rules.pro" } diff --git a/test-util-android/build.gradle b/test-util-android/build.gradle index 9450a8f..fad239d 100644 --- a/test-util-android/build.gradle +++ b/test-util-android/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/test-util-junit5-android/build.gradle b/test-util-junit5-android/build.gradle index 2809993..dce7575 100644 --- a/test-util-junit5-android/build.gradle +++ b/test-util-junit5-android/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/test-util-shared-android/build.gradle b/test-util-shared-android/build.gradle index bb2ffbc..f1ce5d8 100644 --- a/test-util-shared-android/build.gradle +++ b/test-util-shared-android/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" diff --git a/test-util-shared-robolectric/build.gradle b/test-util-shared-robolectric/build.gradle index db2c58d..56f9529 100644 --- a/test-util-shared-robolectric/build.gradle +++ b/test-util-shared-robolectric/build.gradle @@ -4,11 +4,11 @@ plugins { } android { - compileSdk 31 + compileSdk rootProject.ext.compileSdkVersion defaultConfig { - minSdk 21 - targetSdk 31 + minSdk rootProject.ext.minSdkVersion + targetSdk rootProject.ext.targetSdkVersion testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" From 29083518f22bda319147da7c56b98f0be9266260 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Fri, 30 Sep 2022 21:18:57 +0300 Subject: [PATCH 03/10] Issue#35 Jacoco works for Java modules and UnitTests. - Not on AndroidTest (issue reported). - Not aggregated. --- build.gradle | 3 + gradlescripts/jacoco.config.gradle | 88 ++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 gradlescripts/jacoco.config.gradle diff --git a/build.gradle b/build.gradle index 0644343..fd97eea 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ buildscript { ext.compileSdkVersion = 32 ext.minSdkVersion = 21 ext.targetSdkVersion = 32 + ext.jacoco_version = "0.8.8" repositories { mavenCentral() google() @@ -20,6 +21,7 @@ buildscript { classpath "com.google.dagger:hilt-android-gradle-plugin:$hilt_version" classpath "org.jlleitschuh.gradle:ktlint-gradle:10.2.1" classpath "io.gitlab.arturbosch.detekt:detekt-gradle-plugin:$detekt_version" + classpath "org.jacoco:org.jacoco.core:$jacoco_version" } } @@ -50,3 +52,4 @@ apply from: 'gradlescripts/testoptions.gradle' apply from: 'gradlescripts/test.tasks.gradle' apply from: 'gradlescripts/testdependencies.gradle' apply from: 'gradlescripts/disable.test.task.gradle' +apply from: 'gradlescripts/jacoco.config.gradle' diff --git a/gradlescripts/jacoco.config.gradle b/gradlescripts/jacoco.config.gradle new file mode 100644 index 0000000..5ea4d01 --- /dev/null +++ b/gradlescripts/jacoco.config.gradle @@ -0,0 +1,88 @@ +def androidFileFilter = + [ //jdk + 'jdk.internal.*', + // data binding + '**/databinding/*.class', + '**/BR.*', + // android + '**/R.class', + '**/R$*.class', + '**/BuildConfig.*', + '**/Manifest*.*', + '**/*Test*.*', + 'android/**/*.*', + // kotlin + '**/*MapperImpl*.*', + '**/*$ViewInjector*.*', + '**/*$ViewBinder*.*', + '**/BuildConfig.*', + '**/*Component*.*', + '**/*BR*.*', + '**/Manifest*.*', + '**/*$Lambda$*.*', + '**/*Companion*.*', + '**/*Module*.*', + '**/*Dagger*.*', + '**/*Hilt*.*', + '**/*MembersInjector*.*', + '**/*_MembersInjector.class', + '**/*_Factory*.*', + '**/*_Provide*Factory*.*', + '**/*Extensions*.*', + // sealed and data classes + '**/*$Result.*', + '**/*$Result$*.*', + // adapters generated by moshi + '**/*JsonAdapter.*', + // room + '**/*_Impl.class', + '**/*_Impl*.*', + ] + +subprojects { module -> + plugins.withType(JavaPlugin).whenPluginAdded { + configure(module) { + apply plugin: "jacoco" + + jacocoTestReport { + dependsOn test // tests are required to run before generating the report + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: androidFileFilter) + })) + } + } + } + } + + plugins.withId("com.android.application") { + configure(module) { + apply plugin: "jacoco" + + module.android.testOptions.unitTests.all { + jacoco.includeNoLocationClasses = true + jacoco.excludes = androidFileFilter + } + android.buildTypes.debug.enableAndroidTestCoverage true + android.buildTypes.debug.enableUnitTestCoverage true + + jacoco.toolVersion = "$jacoco_version" + } + } + + plugins.withId("com.android.library") { + configure(module) { + apply plugin: "jacoco" + + module.android.testOptions.unitTests.all { + jacoco.includeNoLocationClasses = true + jacoco.excludes = androidFileFilter + } + android.buildTypes.debug.enableAndroidTestCoverage true + android.buildTypes.debug.enableUnitTestCoverage true + + jacoco.toolVersion = "$jacoco_version" + } + } +} \ No newline at end of file From 52713aaa3d0fb6ae2baa7a12d0a779d372e9ca77 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 17 Jan 2023 20:16:12 +0200 Subject: [PATCH 04/10] Fix Hilt RobolectricAuthActivity tests failing --- .../hilt/ui/RobolectricAuthActivityInstrumentedTest.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hilt/hilt-app/src/robolectricTest/java/org/fnives/test/showcase/hilt/ui/RobolectricAuthActivityInstrumentedTest.kt b/hilt/hilt-app/src/robolectricTest/java/org/fnives/test/showcase/hilt/ui/RobolectricAuthActivityInstrumentedTest.kt index 1463aa9..3c0313d 100644 --- a/hilt/hilt-app/src/robolectricTest/java/org/fnives/test/showcase/hilt/ui/RobolectricAuthActivityInstrumentedTest.kt +++ b/hilt/hilt-app/src/robolectricTest/java/org/fnives/test/showcase/hilt/ui/RobolectricAuthActivityInstrumentedTest.kt @@ -18,6 +18,7 @@ import org.fnives.test.showcase.android.testutil.synchronization.idlingresources import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.Disposable import org.fnives.test.showcase.android.testutil.synchronization.idlingresources.IdlingResourceDisposable import org.fnives.test.showcase.hilt.R +import org.fnives.test.showcase.hilt.network.testutil.HttpsConfigurationModuleTemplate import org.fnives.test.showcase.hilt.network.testutil.NetworkSynchronization import org.fnives.test.showcase.hilt.test.shared.di.TestBaseUrlHolder import org.fnives.test.showcase.hilt.test.shared.testutils.storage.TestDatabaseInitialization @@ -56,8 +57,9 @@ class RobolectricAuthActivityInstrumentedTest { testDispatcher = dispatcher TestDatabaseInitialization.dispatcher = dispatcher - mockServerScenarioSetup = MockServerScenarioSetup() - TestBaseUrlHolder.url = mockServerScenarioSetup.start(false) + val (mockServerScenarioSetup, url) = HttpsConfigurationModuleTemplate.startWithHTTPSMockWebServer() + this.mockServerScenarioSetup = mockServerScenarioSetup + TestBaseUrlHolder.url = url hiltRule.inject() val idlingResources = networkSynchronization.networkIdlingResources() From 9964f25e78aaf77ce468f6085e67f452e9756821 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 17 Jan 2023 20:17:04 +0200 Subject: [PATCH 05/10] Issue#35 Jacoco works for both Android and Java Modules. Report can be aggregated into one. The aggregated report contains data from AndroidTest/UnitTest and Java UnitTest --- gradlescripts/jacoco.config.gradle | 96 ++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) diff --git a/gradlescripts/jacoco.config.gradle b/gradlescripts/jacoco.config.gradle index 5ea4d01..c3e4410 100644 --- a/gradlescripts/jacoco.config.gradle +++ b/gradlescripts/jacoco.config.gradle @@ -1,3 +1,4 @@ +// filter of files that shouldn't be part of the report def androidFileFilter = [ //jdk 'jdk.internal.*', @@ -12,6 +13,7 @@ def androidFileFilter = '**/*Test*.*', 'android/**/*.*', // kotlin + '**/META-INF/*', '**/*MapperImpl*.*', '**/*$ViewInjector*.*', '**/*$ViewBinder*.*', @@ -37,6 +39,8 @@ def androidFileFilter = // room '**/*_Impl.class', '**/*_Impl*.*', + // hilt + '**/hilt_aggregated_deps/*' ] subprojects { module -> @@ -53,6 +57,10 @@ subprojects { module -> })) } } + + task unitTest(dependsOn: ["test"]) { + group = "verification" + } } } @@ -68,6 +76,16 @@ subprojects { module -> android.buildTypes.debug.enableUnitTestCoverage true jacoco.toolVersion = "$jacoco_version" + + task jacocoTestReport(dependsOn: ["createDebugUnitTestCoverageReport", "createDebugAndroidTestCoverageReport"]) { + group = "verification" + } + task jacocoUnitTestReport(dependsOn: ["createDebugUnitTestCoverageReport"]) { + group = "verification" + } + task jacocoAndroidTestReport(dependsOn: ["createDebugAndroidTestCoverageReport"]) { + group = "verification" + } } } @@ -83,6 +101,84 @@ subprojects { module -> android.buildTypes.debug.enableUnitTestCoverage true jacoco.toolVersion = "$jacoco_version" + + println(module.projectDir) + def hasAndroidTests = new File("${module.projectDir}/src/androidTest").exists() + def hasUnitTests = new File("${module.projectDir}/src/test").exists() + if (hasUnitTests) { + task jacocoUnitTestReport(dependsOn: ["createDebugUnitTestCoverageReport"]) { + group = "verification" + } + } else { + task jacocoUnitTestReport() { + group = "verification" + } + } + if (hasAndroidTests) { + task jacocoAndroidTestReport(dependsOn: ["createDebugAndroidTestCoverageReport"]) { + group = "verification" + } + } else { + task jacocoAndroidTestReport() { + group = "verification" + } + } + + task jacocoTestReport(dependsOn: ["jacocoUnitTestReport", "jacocoAndroidTestReport"]) { + group = "verification" + } + } + } +} + +configure(rootProject) { + apply plugin: "jacoco" + + def testTypeName = "debug" + if (rootProject.extensions.extraProperties.has("testVariant")) { + testTypeName = rootProject.extensions.extraProperties.getByName("testVariant") + } + + task runTestAndJacocoRootReport(type: JacocoReport, group: 'Coverage reports') { + description = 'Run Tests and Generates report from all subprojects' + + def codeProjects = subprojects.findAll({ it.subprojects.isEmpty() }) + codeProjects.forEach { + dependsOn += ["$it.path:jacocoTestReport"] + } + + finalizedBy("jacocoRootReport") + } + + task jacocoRootReport(type: JacocoReport, group: 'Coverage reports') { + description = 'Generates report from all subprojects' + + def codeProjects = subprojects.findAll({ it.subprojects.isEmpty() }) + sourceDirectories.from = files(codeProjects.collect { "${it.projectDir}/src/main/java" }) + def filetrees = codeProjects.collect { + def javaTree = fileTree(dir: "${it.buildDir}/classes/java/main", excludes: androidFileFilter) + def kotlinTree = fileTree(dir: "${it.buildDir}/classes/kotlin/main", excludes: androidFileFilter) + def androidJavaTree = fileTree(dir: "${it.buildDir}/intermediates/javac/${testTypeName}/classes", excludes: androidFileFilter) + def androidKotlinTree = fileTree(dir: "${it.buildDir}/tmp/kotlin-classes/${testTypeName}", excludes: androidFileFilter) + files([javaTree, kotlinTree, androidJavaTree, androidKotlinTree]) + }.flatten() + + classDirectories.from = files(filetrees) + executionData.from = files( + codeProjects.collect { + [ + fileTree(dir: "${it.buildDir}/outputs/code_coverage/${testTypeName}AndroidTest/connected/", includes: ["**/*.ec", "**/*.exec"]), // androidTest + fileTree(dir: "${it.buildDir}/outputs/unit_test_code_coverage/", includes: ["**/*.ec", "**/*.exec"]), // android unitTest + "${it.buildDir}/jacoco/test.exec" // java + ] + }.flatten() + ) + + reports { + html { + required = true + destination file("${buildDir}/coverage-report") + } } } } \ No newline at end of file From 931783f6f116fee1baaa44e0dc4642dac0d28aec Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Tue, 17 Jan 2023 20:17:22 +0200 Subject: [PATCH 06/10] Update module order in settings.gradle --- settings.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.gradle b/settings.gradle index eb7803a..34d6cc5 100644 --- a/settings.gradle +++ b/settings.gradle @@ -12,7 +12,7 @@ include ':app-shared-test' include ':hilt:hilt-core' include ':hilt:hilt-network' include ':hilt:hilt-app' -include ':examplecase:example-navcontroller' -include ':examplecase:example-navcontroller-shared-test' include ':hilt:hilt-network-di-test-util' include ':hilt:hilt-app-shared-test' +include ':examplecase:example-navcontroller' +include ':examplecase:example-navcontroller-shared-test' From ccc67dbcb7ee044b6c7ff19cdb545fb6a33f29da Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Wed, 18 Jan 2023 11:26:23 +0200 Subject: [PATCH 07/10] Issue#35 Add documentation into the gradle file --- gradlescripts/jacoco.config.gradle | 216 +++++++++++++++++------------ 1 file changed, 125 insertions(+), 91 deletions(-) diff --git a/gradlescripts/jacoco.config.gradle b/gradlescripts/jacoco.config.gradle index c3e4410..d6cd8a6 100644 --- a/gradlescripts/jacoco.config.gradle +++ b/gradlescripts/jacoco.config.gradle @@ -43,105 +43,120 @@ def androidFileFilter = '**/hilt_aggregated_deps/*' ] -subprojects { module -> - plugins.withType(JavaPlugin).whenPluginAdded { - configure(module) { - apply plugin: "jacoco" - jacocoTestReport { - dependsOn test // tests are required to run before generating the report +/ ** + * Setup Jacoco for Android module. + * - applies Plugin to the module. + * - sets up the "debug" build type for jacoco + * - creates tasks: + * - if androidTest folder exists creates a jacocoAndroidTestReport alias for the jacoco coverageReport, otherwise an empty task. + * - if test folder exists creates a jacocoUnitTestReport alias for the jacoco coverageReport, otherwise an empty task. + * - creates jacocoTestReport which runs both alias tasks created before. + * + * Note: "jacocoTestReport" is the task name which is default for Java Modules. + * / - afterEvaluate { - classDirectories.setFrom(files(classDirectories.files.collect { - fileTree(dir: it, exclude: androidFileFilter) - })) - } - } +def setupAndroidJacoco(Project module, ArrayList fileFilter, String jacocoVersion) { + configure(module) { + apply plugin: "jacoco" - task unitTest(dependsOn: ["test"]) { - group = "verification" - } + module.android.testOptions.unitTests.all { + jacoco.includeNoLocationClasses = true + jacoco.excludes = fileFilter } - } + android.buildTypes.debug.enableAndroidTestCoverage true + android.buildTypes.debug.enableUnitTestCoverage true - plugins.withId("com.android.application") { - configure(module) { - apply plugin: "jacoco" + jacoco.toolVersion = "$jacocoVersion" - module.android.testOptions.unitTests.all { - jacoco.includeNoLocationClasses = true - jacoco.excludes = androidFileFilter - } - android.buildTypes.debug.enableAndroidTestCoverage true - android.buildTypes.debug.enableUnitTestCoverage true - - jacoco.toolVersion = "$jacoco_version" - - task jacocoTestReport(dependsOn: ["createDebugUnitTestCoverageReport", "createDebugAndroidTestCoverageReport"]) { - group = "verification" - } + def hasAndroidTests = new File("${module.projectDir}/src/androidTest").exists() + def hasUnitTests = new File("${module.projectDir}/src/test").exists() + if (hasUnitTests) { task jacocoUnitTestReport(dependsOn: ["createDebugUnitTestCoverageReport"]) { group = "verification" } - task jacocoAndroidTestReport(dependsOn: ["createDebugAndroidTestCoverageReport"]) { + } else { + task jacocoUnitTestReport() { group = "verification" } } - } - - plugins.withId("com.android.library") { - configure(module) { - apply plugin: "jacoco" - - module.android.testOptions.unitTests.all { - jacoco.includeNoLocationClasses = true - jacoco.excludes = androidFileFilter - } - android.buildTypes.debug.enableAndroidTestCoverage true - android.buildTypes.debug.enableUnitTestCoverage true - - jacoco.toolVersion = "$jacoco_version" - - println(module.projectDir) - def hasAndroidTests = new File("${module.projectDir}/src/androidTest").exists() - def hasUnitTests = new File("${module.projectDir}/src/test").exists() - if (hasUnitTests) { - task jacocoUnitTestReport(dependsOn: ["createDebugUnitTestCoverageReport"]) { - group = "verification" - } - } else { - task jacocoUnitTestReport() { - group = "verification" - } - } - if (hasAndroidTests) { - task jacocoAndroidTestReport(dependsOn: ["createDebugAndroidTestCoverageReport"]) { - group = "verification" - } - } else { - task jacocoAndroidTestReport() { - group = "verification" - } - } - - task jacocoTestReport(dependsOn: ["jacocoUnitTestReport", "jacocoAndroidTestReport"]) { + if (hasAndroidTests) { + task jacocoAndroidTestReport(dependsOn: ["createDebugAndroidTestCoverageReport"]) { group = "verification" } + } else { + task jacocoAndroidTestReport() { + group = "verification" + } + } + + task jacocoTestReport(dependsOn: ["jacocoUnitTestReport", "jacocoAndroidTestReport"]) { + group = "verification" + } + } +} + +/ ** + * Setup Jacoco for Java module. + * - applies Plugin to the module. + * - updates the `jacocoTestReport` task to ignore the `androidFileFilter` + * - ensures tests run before `jacocoTestReport` + * / + +def setupJavaJacoco(Project module, ArrayList fileFilter) { + configure(module) { + apply plugin: "jacoco" + + jacocoTestReport { + dependsOn test // tests are required to run before generating the report + + afterEvaluate { + classDirectories.setFrom(files(classDirectories.files.collect { + fileTree(dir: it, exclude: fileFilter) + })) + } } } } +/ ** + * Setup Jacoco for submodules based on their android or java module type + */ +subprojects { module -> + plugins.withType(JavaPlugin).whenPluginAdded { + setupJavaJacoco(module, androidFileFilter) + } + + plugins.withId("com.android.application") { + setupAndroidJacoco(module, androidFileFilter, "$jacoco_version") + } + + plugins.withId("com.android.library") { + setupAndroidJacoco(module, androidFileFilter, "$jacoco_version") + } +} + +/ ** + * Setup Aggregation tasks for Jacoco. + * - jacocoRootReport: can be used to generate the report after submodules `jacocoTestReport` has been ran at least once. + * - runTestAndJacocoRootReport: calls the `jacocoTestReport` of each submodule then calls `jacocoRootReport` for aggregation. + * + * Context, how the aggregated report works: + * The jacoco tasks created by the plugin generate .ec and .exec Execution-Data files in specific locations. + * - These Execution-Data files are all pulled into one `JacocoReport` task (`executionData.from`). + * - All the source files from all the submodules are pulled into the same `JacocoReport` task (`sourceDirectories.from`) + * - All the class files from all the submodules are pulled into the same `JacocoReport` task (`classDirectories.from`) + * Then finally the report is configured to be generated at root `build\coverage-report` + * / configure(rootProject) { apply plugin: "jacoco" - def testTypeName = "debug" - if (rootProject.extensions.extraProperties.has("testVariant")) { - testTypeName = rootProject.extensions.extraProperties.getByName("testVariant") - } task runTestAndJacocoRootReport(type: JacocoReport, group: 'Coverage reports') { description = 'Run Tests and Generates report from all subprojects' + // add all non empty subProjects `jacocoTestReport` task as a dependency. + // note: these tasks are default Jacoco Task for Java and have been added above for Android modules. def codeProjects = subprojects.findAll({ it.subprojects.isEmpty() }) codeProjects.forEach { dependsOn += ["$it.path:jacocoTestReport"] @@ -155,24 +170,43 @@ configure(rootProject) { def codeProjects = subprojects.findAll({ it.subprojects.isEmpty() }) sourceDirectories.from = files(codeProjects.collect { "${it.projectDir}/src/main/java" }) - def filetrees = codeProjects.collect { - def javaTree = fileTree(dir: "${it.buildDir}/classes/java/main", excludes: androidFileFilter) - def kotlinTree = fileTree(dir: "${it.buildDir}/classes/kotlin/main", excludes: androidFileFilter) - def androidJavaTree = fileTree(dir: "${it.buildDir}/intermediates/javac/${testTypeName}/classes", excludes: androidFileFilter) - def androidKotlinTree = fileTree(dir: "${it.buildDir}/tmp/kotlin-classes/${testTypeName}", excludes: androidFileFilter) - files([javaTree, kotlinTree, androidJavaTree, androidKotlinTree]) - }.flatten() - classDirectories.from = files(filetrees) - executionData.from = files( - codeProjects.collect { - [ - fileTree(dir: "${it.buildDir}/outputs/code_coverage/${testTypeName}AndroidTest/connected/", includes: ["**/*.ec", "**/*.exec"]), // androidTest - fileTree(dir: "${it.buildDir}/outputs/unit_test_code_coverage/", includes: ["**/*.ec", "**/*.exec"]), // android unitTest - "${it.buildDir}/jacoco/test.exec" // java - ] - }.flatten() - ) + def classFileTrees = codeProjects.collect { + def javaClassFilesInJavaModuleTree = fileTree( + dir: "${it.buildDir}/classes/java/main", + excludes: androidFileFilter + ) + def kotlinClassFilesInJavaModuleTree = fileTree( + dir: "${it.buildDir}/classes/kotlin/main", + excludes: androidFileFilter + ) + def javaClassFilesInAndroidModuleTree = fileTree( + dir: "${it.buildDir}/intermediates/javac/${testTypeName}/classes", + excludes: androidFileFilter + ) + def kotlinClassFilesInAndroidModuleTree = fileTree( + dir: "${it.buildDir}/tmp/kotlin-classes/${testTypeName}", + excludes: androidFileFilter + ) + + files([javaClassFilesInJavaModuleTree, kotlinClassFilesInJavaModuleTree, javaClassFilesInAndroidModuleTree, kotlinClassFilesInAndroidModuleTree]) + }.flatten() + classDirectories.from = files(classFileTrees) + + def executionDataFiles = codeProjects.collect { + def androidTestExecutionData = fileTree( + dir: "${it.buildDir}/outputs/code_coverage/${testTypeName}AndroidTest/connected/", + includes: ["**/*.ec", "**/*.exec"] + ) + def androidUnitTestExecutionData = fileTree( + dir: "${it.buildDir}/outputs/unit_test_code_coverage/", + includes: ["**/*.ec", "**/*.exec"] + ) + def javaUnitTestExecutionData = "${it.buildDir}/jacoco/test.exec" + + [androidTestExecutionData, androidUnitTestExecutionData, javaUnitTestExecutionData] + }.flatten() + executionData.from = files(executionDataFiles) reports { html { From c374aa0d5d180ce58e6bfbe1831bc1657b660ff4 Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Wed, 18 Jan 2023 12:01:58 +0200 Subject: [PATCH 08/10] Issue#35 Update readme.md with Coverage section --- README.md | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/README.md b/README.md index 4f89e6b..a432d78 100644 --- a/README.md +++ b/README.md @@ -230,5 +230,41 @@ androidTestImplementation "org.fnives.android.testutil:android:" androidTestImplementation "org.fnives.android.testutil:shared-android:" // test-util-shared-android ``` +## Code Coverage Report + +For Code Coverage Reporting, Jacoco is setup in [jacoco.config.gradle](./gradlescripts/jacoco.config.gradle). + +- Each sub module has it's own code coverage report, enabled by the gradle script. +- Additionally it contains gradle task for an aggregated code coverage report for the project as a whole. + +Feel free to use that script and tweak it for your project and module setup. + +The script is documented, to the best of my understanding, but specific to this project, not prepared for multiple buildFlavours or different buildTypes than debug. + +### Sub module reports +To run tests and Jacoco report for a submodule, run task `jacocoTestReport`: +- for java it will run unit tests and creates a report +- for android it will run jacocoAndroidTestReport and jacocoUnitTestReport and create 2 separate reports. + +> Note: +> - jacocoAndroidTestReport is alias to createDebugAndroidTestCoverageReport +> - jacocoUnitTestReport is alias to createDebugUnitTestCoverageReport + +### Aggregated reports +To see an aggregated code coverage report: +- task `jacocoRootReport` will pull together all the submodules report and create a single one from them ($projectDir/build/coverage-report). +- task `runTestAndJacocoRootReport` will run all the sub modules reports and tests then run `jacocoRootReport`. + +### Issues +*One issue, is that the androidTest reports don't work with the sharedTest module setup, this issue is reported [here](https://issuetracker.google.com/issues/250130118)* + +By shared test module setup I mean a module like `app-shared-test`, which has a dependency graph of: +- app-shared-test -> app.main +- app.test -> app-shared-test + +### Reference +Here are the two articles I used for the jacoco setup script: [jacoco-in-android](https://medium.com/swlh/multi-module-multi-flavored-test-coverage-with-jacoco-in-android-bc4fb4d135a3) +[aggregate-test-coverage](https://lkrnac.net/blog/2016/10/aggregate-test-coverage-report/). + ## License [License file](./LICENSE) From 49b7b630f06ca91806b6fc73255a7a1b9c7cfdcc Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Wed, 18 Jan 2023 12:15:05 +0200 Subject: [PATCH 09/10] PR#143 Fix lint warnings --- app/src/main/res/values-night/themes.xml | 2 +- app/src/main/res/values/themes.xml | 2 +- hilt/hilt-app/src/main/res/values-night/themes.xml | 2 +- hilt/hilt-app/src/main/res/values/themes.xml | 2 +- .../idlingresources/OkHttp3IdlingResource.kt | 6 +----- 5 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml index 4e197d3..c3400cc 100644 --- a/app/src/main/res/values-night/themes.xml +++ b/app/src/main/res/values-night/themes.xml @@ -10,7 +10,7 @@ @color/teal_200 @color/black - ?attr/colorPrimaryVariant + ?attr/colorPrimaryVariant \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 64b01cd..3255b93 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -10,7 +10,7 @@ @color/teal_700 @color/black - ?attr/colorPrimaryVariant + ?attr/colorPrimaryVariant \ No newline at end of file diff --git a/hilt/hilt-app/src/main/res/values-night/themes.xml b/hilt/hilt-app/src/main/res/values-night/themes.xml index 4e197d3..c3400cc 100644 --- a/hilt/hilt-app/src/main/res/values-night/themes.xml +++ b/hilt/hilt-app/src/main/res/values-night/themes.xml @@ -10,7 +10,7 @@ @color/teal_200 @color/black - ?attr/colorPrimaryVariant + ?attr/colorPrimaryVariant \ No newline at end of file diff --git a/hilt/hilt-app/src/main/res/values/themes.xml b/hilt/hilt-app/src/main/res/values/themes.xml index 64b01cd..3255b93 100644 --- a/hilt/hilt-app/src/main/res/values/themes.xml +++ b/hilt/hilt-app/src/main/res/values/themes.xml @@ -10,7 +10,7 @@ @color/teal_700 @color/black - ?attr/colorPrimaryVariant + ?attr/colorPrimaryVariant \ No newline at end of file diff --git a/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/synchronization/idlingresources/OkHttp3IdlingResource.kt b/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/synchronization/idlingresources/OkHttp3IdlingResource.kt index 258067b..f1cf274 100644 --- a/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/synchronization/idlingresources/OkHttp3IdlingResource.kt +++ b/test-util-android/src/main/java/org/fnives/test/showcase/android/testutil/synchronization/idlingresources/OkHttp3IdlingResource.kt @@ -1,7 +1,6 @@ package org.fnives.test.showcase.android.testutil.synchronization.idlingresources import androidx.annotation.CheckResult -import androidx.annotation.NonNull import androidx.test.espresso.IdlingResource import okhttp3.Dispatcher import okhttp3.OkHttpClient @@ -55,10 +54,7 @@ class OkHttp3IdlingResource private constructor( * this instance using `Espresso.registerIdlingResources`. */ @CheckResult - @NonNull - fun create(@NonNull name: String?, @NonNull client: OkHttpClient?): OkHttp3IdlingResource { - if (name == null) throw NullPointerException("name == null") - if (client == null) throw NullPointerException("client == null") + fun create(name: String, client: OkHttpClient): OkHttp3IdlingResource { return OkHttp3IdlingResource(name, client.dispatcher) } From a37c6a4a3de119d472df32bb4c776c0da0af68fe Mon Sep 17 00:00:00 2001 From: Gergely Hegedus Date: Wed, 18 Jan 2023 15:33:12 +0200 Subject: [PATCH 10/10] PR#143 Fix tests can't run on API 21 Seems like jacoco androidTestCoverage breaks tests on API 21 with Resource.NotFound for some reason. It works well on newer API levels. To workaround it, we simply disable that coverage for CI. --- .github/workflows/pull-request-jobs.yml | 2 +- README.md | 3 ++- gradlescripts/jacoco.config.gradle | 6 ++++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pull-request-jobs.yml b/.github/workflows/pull-request-jobs.yml index 920f129..240c695 100644 --- a/.github/workflows/pull-request-jobs.yml +++ b/.github/workflows/pull-request-jobs.yml @@ -122,7 +122,7 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: ./gradlew connectedDebugAndroidTest + script: ./gradlew connectedDebugAndroidTest -PdisableAndroidTestCoverage=true - name: Upload Test Results uses: actions/upload-artifact@v2 if: always() diff --git a/README.md b/README.md index a432d78..3155dff 100644 --- a/README.md +++ b/README.md @@ -256,7 +256,8 @@ To see an aggregated code coverage report: - task `runTestAndJacocoRootReport` will run all the sub modules reports and tests then run `jacocoRootReport`. ### Issues -*One issue, is that the androidTest reports don't work with the sharedTest module setup, this issue is reported [here](https://issuetracker.google.com/issues/250130118)* +- One issue, is that the androidTest reports don't work with the sharedTest module setup, this issue is reported [here](https://issuetracker.google.com/issues/250130118) +- Another issue, is that seems like the tests fail with Resource.NotFound on API 21 if `enableAndroidTestCoverage` is true, so I disabled that for CI. By shared test module setup I mean a module like `app-shared-test`, which has a dependency graph of: - app-shared-test -> app.main diff --git a/gradlescripts/jacoco.config.gradle b/gradlescripts/jacoco.config.gradle index d6cd8a6..bfd9978 100644 --- a/gradlescripts/jacoco.config.gradle +++ b/gradlescripts/jacoco.config.gradle @@ -64,8 +64,10 @@ def setupAndroidJacoco(Project module, ArrayList fileFilter, String jaco jacoco.includeNoLocationClasses = true jacoco.excludes = fileFilter } - android.buildTypes.debug.enableAndroidTestCoverage true - android.buildTypes.debug.enableUnitTestCoverage true + // on API 21 enableAndroidTestCoverage makes the tests crash with resource not found issue + def disableAndroidTestCoverage = findProperty("disableAndroidTestCoverage") ?: false + android.buildTypes.debug.enableAndroidTestCoverage = !disableAndroidTestCoverage + android.buildTypes.debug.enableUnitTestCoverage = true jacoco.toolVersion = "$jacocoVersion"