From 3b06576d2186c6b673bfd4cfafb057c9796488b6 Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Tue, 9 Jan 2024 10:49:42 +0100 Subject: [PATCH] Upgrade Kover to 0.7.5 --- .github/workflows/nightlyReports.yml | 4 +- .github/workflows/tests.yml | 6 +- app/build.gradle.kts | 193 +++++++++++++++++++++++++++ build.gradle.kts | 158 +--------------------- changelog.d/1782.misc | 1 + docs/_developer_onboarding.md | 10 +- gradle/libs.versions.toml | 2 +- 7 files changed, 210 insertions(+), 164 deletions(-) create mode 100644 changelog.d/1782.misc diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 3cb4119f99..a7eaee7828 100644 --- a/.github/workflows/nightlyReports.yml +++ b/.github/workflows/nightlyReports.yml @@ -33,7 +33,7 @@ jobs: run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES - name: πŸ“ˆ Generate kover report and verify coverage - run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + run: ./gradlew :app:koverHtmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: βœ… Upload kover report if: always() @@ -41,7 +41,7 @@ jobs: with: name: kover-results path: | - **/build/reports/kover/merged + **/build/reports/kover - name: πŸ”Š Publish results to Sonar env: diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6188145d67..680a3a05ea 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,7 +55,7 @@ jobs: run: ./gradlew verifyPaparazziDebug $CI_GRADLE_ARG_PROPERTIES - name: πŸ“ˆGenerate kover report and verify coverage - run: ./gradlew koverMergedReport koverMergedVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true + run: ./gradlew :app:koverHtmlReport :app:koverVerify $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: 🚫 Upload kover failed coverage reports if: failure() @@ -63,7 +63,7 @@ jobs: with: name: kover-error-report path: | - **/kover/merged/verification/errors.txt + app/build/reports/kover/verify.err - name: βœ… Upload kover report (disabled) if: always() @@ -83,4 +83,4 @@ jobs: if: always() uses: codecov/codecov-action@v3 # with: - # files: build/reports/kover/merged/xml/report.xml + # files: build/reports/kover/xml/report.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 91f924af91..c9c0603b3e 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -190,6 +190,199 @@ knit { } } +/** + * Kover configuration + */ + +dependencies { + // Add all sub projects to kover except some of them + project.rootProject.subprojects + .filter { + it.project.projectDir.resolve("build.gradle.kts").exists() + } + .map { it.path } + .sorted() + .filter { + it !in listOf( + ":app", + ":samples", + ":anvilannotations", + ":anvilcodegen", + ":samples:minimal", + ":tests:testutils", + // Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix + // SDK api, so it is not really relevant to unit test it: there is no logic to test. + ":libraries:matrix:impl", + // Exclude modules which are not Android libraries + // See https://github.com/Kotlin/kotlinx-kover/issues/312 + ":appconfig", + ":libraries:core", + ":libraries:coroutines", + ":libraries:di", + ":libraries:rustsdk", + ":libraries:textcomposer:lib", + ) + } + .forEach { + // println("Add $it to kover") + kover(project(it)) + } +} + +// https://kotlin.github.io/kotlinx-kover/ +// Run `./gradlew :app:koverHtmlReport` to get report at ./app/build/reports/kover +// Run `./gradlew :app:koverXmlReport` to get XML report +koverReport { + filters { + excludes { + classes( + // Exclude generated classes. + "*_ModuleKt", + "anvil.hint.binding.io.element.*", + "anvil.hint.merge.*", + "anvil.hint.multibinding.io.element.*", + "anvil.module.*", + "com.airbnb.android.showkase*", + "io.element.android.libraries.designsystem.showkase.*", + "io.element.android.x.di.DaggerAppComponent*", + "*_Factory", + "*_Factory_Impl", + "*_Factory$*", + "*_Module", + "*_Module$*", + "*Module_Provides*", + "Dagger*Component*", + "*ComposableSingletons$*", + "*_AssistedFactory_Impl*", + "*BuildConfig", + // Generated by Showkase + "*Ioelementandroid*PreviewKt$*", + "*Ioelementandroid*PreviewKt", + // Other + // We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro) + "*Node", + "*Node$*", + "*Presenter\$present\$*", + // Forked from compose + "io.element.android.libraries.designsystem.theme.components.bottomsheet.*", + ) + annotatedBy( + "io.element.android.libraries.designsystem.preview.PreviewsDayNight", + "io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight", + ) + } + } + + defaults { + // add reports of 'release' Android build variant to default reports + mergeWith("release") + + verify { + onCheck = true + // General rule: minimum code coverage. + rule("Global minimum code coverage.") { + isEnabled = true + entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION + bound { + minValue = 65 + // Setting a max value, so that if coverage is bigger, it means that we have to change minValue. + // For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update + // minValue to 25 and maxValue to 35. + maxValue = 75 + metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION + aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE + } + } + // Rule to ensure that coverage of Presenters is sufficient. + rule("Check code coverage of presenters") { + isEnabled = true + entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS + filters { + includes { + classes( + "*Presenter", + ) + } + excludes { + classes( + "*Fake*Presenter", + "io.element.android.appnav.loggedin.LoggedInPresenter$*", + // Some options can't be tested at the moment + "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*", + "*Presenter\$present\$*", + ) + } + } + bound { + minValue = 85 + metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION + aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE + } + } + // Rule to ensure that coverage of States is sufficient. + rule("Check code coverage of states") { + isEnabled = true + entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS + filters { + includes { + classes( + "^*State$", + ) + } + excludes { + classes( + "io.element.android.appnav.root.RootNavState*", + "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*", + "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*", + "io.element.android.libraries.matrix.api.room.RoomMembershipState*", + "io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*", + "io.element.android.libraries.push.impl.notifications.NotificationState*", + "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState", + "io.element.android.features.messages.impl.media.local.LocalMediaViewState", + "io.element.android.features.location.impl.map.MapState*", + "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*", + "io.element.android.libraries.designsystem.swipe.SwipeableActionsState*", + "io.element.android.features.messages.impl.timeline.components.ExpandableState*", + "io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*", + "io.element.android.libraries.maplibre.compose.CameraPositionState*", + "io.element.android.libraries.maplibre.compose.SaveableCameraPositionState", + "io.element.android.libraries.maplibre.compose.SymbolState*", + "io.element.android.features.ftue.api.state.*", + "io.element.android.features.ftue.impl.welcome.state.*", + ) + } + } + bound { + minValue = 90 + metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION + aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE + } + } + // Rule to ensure that coverage of Views is sufficient (deactivated for now). + rule("Check code coverage of views") { + isEnabled = true + entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.CLASS + filters { + includes { + classes( + "*ViewKt", + ) + } + } + bound { + // TODO Update this value, for now there are too many missing tests. + minValue = 0 + metric = kotlinx.kover.gradle.plugin.dsl.MetricType.INSTRUCTION + aggregation = kotlinx.kover.gradle.plugin.dsl.AggregationType.COVERED_PERCENTAGE + } + } + } + } + + androidReports("release") { + } +} + dependencies { allLibrariesImpl() allServicesImpl() diff --git a/build.gradle.kts b/build.gradle.kts index 4356d1ac1e..09e1f5880f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,4 @@ import com.google.devtools.ksp.gradle.KspTask -import kotlinx.kover.api.KoverTaskExtension import org.apache.tools.ant.taskdefs.optional.ReplaceRegExp import org.jetbrains.kotlin.cli.common.toBooleanLenient @@ -41,7 +40,7 @@ plugins { alias(libs.plugins.ktlint) alias(libs.plugins.dependencygraph) alias(libs.plugins.sonarqube) - alias(libs.plugins.kover) + alias(libs.plugins.kover) apply false } tasks.register("clean").configure { @@ -164,156 +163,7 @@ allprojects { } allprojects { - apply(plugin = "kover") -} - -// https://kotlin.github.io/kotlinx-kover/ -// Run `./gradlew koverMergedHtmlReport` to get report at ./build/reports/kover -// Run `./gradlew koverMergedReport` to also get XML report -koverMerged { - enable() - - filters { - classes { - excludes.addAll( - listOf( - // Exclude generated classes. - "*_ModuleKt", - "anvil.hint.binding.io.element.*", - "anvil.hint.merge.*", - "anvil.hint.multibinding.io.element.*", - "anvil.module.*", - "com.airbnb.android.showkase*", - "io.element.android.libraries.designsystem.showkase.*", - "io.element.android.x.di.DaggerAppComponent*", - "*_Factory", - "*_Factory_Impl", - "*_Factory$*", - "*_Module", - "*_Module$*", - "*Module_Provides*", - "Dagger*Component*", - "*ComposableSingletons$*", - "*_AssistedFactory_Impl*", - "*BuildConfig", - // Generated by Showkase - "*Ioelementandroid*PreviewKt$*", - "*Ioelementandroid*PreviewKt", - // Other - // We do not cover Nodes (normally covered by maestro, but code coverage is not computed with maestro) - "*Node", - "*Node$*", - // Exclude `:libraries:matrix:impl` module, it contains only wrappers to access the Rust Matrix SDK api, so it is not really relevant to unit test it: there is no logic to test. - "io.element.android.libraries.matrix.impl.*", - "*Presenter\$present\$*", - // Forked from compose - "io.element.android.libraries.designsystem.theme.components.bottomsheet.*", - ) - ) - } - - annotations { - excludes.addAll( - listOf( - "*Preview", - ) - ) - } - - projects { - excludes.addAll( - listOf( - ":anvilannotations", - ":anvilcodegen", - ":samples:minimal", - ":tests:testutils", - ) - ) - } - } - - // Run ./gradlew koverMergedVerify to check the rules. - verify { - // Does not seems to work, so also run the task manually on the workflow. - onCheck.set(true) - // General rule: minimum code coverage. - rule { - name = "Global minimum code coverage." - target = kotlinx.kover.api.VerificationTarget.ALL - bound { - minValue = 65 - // Setting a max value, so that if coverage is bigger, it means that we have to change minValue. - // For instance if we have minValue = 20 and maxValue = 30, and current code coverage is now 31.32%, update - // minValue to 25 and maxValue to 35. - maxValue = 75 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - // Rule to ensure that coverage of Presenters is sufficient. - rule { - name = "Check code coverage of presenters" - target = kotlinx.kover.api.VerificationTarget.CLASS - overrideClassFilter { - includes += "*Presenter" - excludes += "*Fake*Presenter" - excludes += "io.element.android.appnav.loggedin.LoggedInPresenter$*" - // Some options can't be tested at the moment - excludes += "io.element.android.features.preferences.impl.developer.DeveloperSettingsPresenter$*" - excludes += "*Presenter\$present\$*" - } - bound { - minValue = 85 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - // Rule to ensure that coverage of States is sufficient. - rule { - name = "Check code coverage of states" - target = kotlinx.kover.api.VerificationTarget.CLASS - overrideClassFilter { - includes += "^*State$" - excludes += "io.element.android.appnav.root.RootNavState*" - excludes += "io.element.android.libraries.matrix.api.timeline.item.event.OtherState$*" - excludes += "io.element.android.libraries.matrix.api.timeline.item.event.EventSendState$*" - excludes += "io.element.android.libraries.matrix.api.room.RoomMembershipState*" - excludes += "io.element.android.libraries.matrix.api.room.MatrixRoomMembersState*" - excludes += "io.element.android.libraries.push.impl.notifications.NotificationState*" - excludes += "io.element.android.features.messages.impl.media.local.pdf.PdfViewerState" - excludes += "io.element.android.features.messages.impl.media.local.LocalMediaViewState" - excludes += "io.element.android.features.location.impl.map.MapState*" - excludes += "io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState*" - excludes += "io.element.android.libraries.designsystem.swipe.SwipeableActionsState*" - excludes += "io.element.android.features.messages.impl.timeline.components.ExpandableState*" - excludes += "io.element.android.features.messages.impl.timeline.model.bubble.BubbleState*" - excludes += "io.element.android.libraries.maplibre.compose.CameraPositionState*" - excludes += "io.element.android.libraries.maplibre.compose.SaveableCameraPositionState" - excludes += "io.element.android.libraries.maplibre.compose.SymbolState*" - excludes += "io.element.android.features.ftue.api.state.*" - excludes += "io.element.android.features.ftue.impl.welcome.state.*" - } - bound { - minValue = 90 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - // Rule to ensure that coverage of Views is sufficient (deactivated for now). - rule { - name = "Check code coverage of views" - target = kotlinx.kover.api.VerificationTarget.CLASS - overrideClassFilter { - includes += "*ViewKt" - } - bound { - // TODO Update this value, for now there are too many missing tests. - minValue = 0 - counter = kotlinx.kover.api.CounterType.INSTRUCTION - valueType = kotlinx.kover.api.VerificationValueType.COVERED_PERCENTAGE - } - } - } + apply(plugin = "org.jetbrains.kotlinx.kover") } // When running on the CI, run only debug test variants @@ -328,10 +178,12 @@ if (isCiBuild) { allprojects { afterEvaluate { tasks.withType().configureEach { + /* TODO extensions.configure { val enabled = name.contains("debug", ignoreCase = true) - isDisabled.set(!enabled) + disabledForProject.set(!enabled) } + */ } } } diff --git a/changelog.d/1782.misc b/changelog.d/1782.misc new file mode 100644 index 0000000000..2e716795d9 --- /dev/null +++ b/changelog.d/1782.misc @@ -0,0 +1 @@ +Migrate to Kover 0.7.X diff --git a/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index b4ea411710..9d5bdafb7a 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -344,26 +344,26 @@ implementation of our interfaces. Mocking can be used to mock Android framework [kover](https://github.com/Kotlin/kotlinx-kover) is used to compute code coverage. Only have unit tests can produce code coverage result. Running Maestro does not participate to the code coverage results. -Kover configuration is defined in the main [build.gradle.kts](../build.gradle.kts) file. +Kover configuration is defined in the app [build.gradle.kts](../app/build.gradle.kts) file. To compute the code coverage, run: ```bash -./gradlew koverMergedReport +./gradlew :app:koverHtmlReport ``` -and open the Html report: [../build/reports/kover/merged/html/index.html](../build/reports/kover/merged/html/index.html) +and open the Html report: [../app/build/reports/kover/html/index.html](../app/build/reports/kover/html/index.html) To ensure that the code coverage threshold are OK, you can run ```bash -./gradlew koverMergedVerify +./gradlew :app:koverVerify ``` Note that the CI performs this check on every pull requests. Also, if the rule `Global minimum code coverage.` is in error because code coverage is `> maxValue`, `minValue` and `maxValue` can be updated for this rule in -the file [build.gradle.kts](../build.gradle.kts) (you will see further instructions there). +the file [build.gradle.kts](../app/build.gradle.kts) (you will see further instructions there). ### Other points diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fe540c5b10..411d16ed8c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -215,7 +215,7 @@ dependencygraph = "com.savvasdalkitsis.module-dependency-graph:0.12" dependencycheck = "org.owasp.dependencycheck:9.0.8" dependencyanalysis = { id = "com.autonomousapps.dependency-analysis", version.ref = "dependencyAnalysis" } paparazzi = "app.cash.paparazzi:1.3.1" -kover = "org.jetbrains.kotlinx.kover:0.6.1" +kover = "org.jetbrains.kotlinx.kover:0.7.5" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } firebaseAppDistribution = { id = "com.google.firebase.appdistribution", version.ref = "firebaseAppDistribution" } knit = { id = "org.jetbrains.kotlinx.knit", version = "0.5.0" }