diff --git a/.github/workflows/nightlyReports.yml b/.github/workflows/nightlyReports.yml index 5562a67cec..958283c679 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 koverHtmlReportRelease koverVerifyRelease $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 87f46cb47a..043653d26c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -55,15 +55,16 @@ 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 koverHtmlReportRelease koverVerifyRelease $CI_GRADLE_ARG_PROPERTIES -Pci-build=true - name: 🚫 Upload kover failed coverage reports if: failure() uses: actions/upload-artifact@v3 with: name: kover-error-report + # TODO path: | - **/kover/merged/verification/errors.txt + **/kover/verify.err - name: βœ… Upload kover report (disabled) if: always() @@ -83,4 +84,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 c8e11927d1..a6346d1d06 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -194,6 +194,179 @@ 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", + ) + } + .forEach { + // println("Add $it to kover") + kover(project(it)) + } +} + +// https://kotlin.github.io/kotlinx-kover/ +// Run `./gradlew koverHtmlReportRelease` to get report at ./build/reports/kover +// Run `./gradlew koverXmlReportRelease` 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$*", + // 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.*", + ) + annotatedBy( + "*Preview", + ) + } + } + + defaults { + verify { + onCheck = true + // General rule: minimum code coverage. + rule("Global minimum code coverage.") { + entity = kotlinx.kover.gradle.plugin.dsl.GroupingEntityType.APPLICATION + bound { + minValue = 60 + // 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 = 70 + 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") { + 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") { + 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") { + 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 + } + } + } + } +} + dependencies { allLibrariesImpl() allServicesImpl() diff --git a/build.gradle.kts b/build.gradle.kts index b2e4ac3c0f..99418e8da7 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 @@ -159,156 +158,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 = 60 - // 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 = 70 - 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 @@ -323,10 +173,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/docs/_developer_onboarding.md b/docs/_developer_onboarding.md index a100a2beba..0a59f5be26 100644 --- a/docs/_developer_onboarding.md +++ b/docs/_developer_onboarding.md @@ -349,15 +349,15 @@ Kover configuration is defined in the main [build.gradle.kts](../build.gradle.kt To compute the code coverage, run: ```bash -./gradlew koverMergedReport +./gradlew koverHtmlReportRelease ``` -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 koverVerifyRelease ``` Note that the CI performs this check on every pull requests. diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b9b775b1a3..be0acc5b25 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -213,7 +213,7 @@ dependencygraph = { id = "com.savvasdalkitsis.module-dependency-graph", version. dependencycheck = { id = "org.owasp.dependencycheck", version.ref = "dependencycheck" } 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.4" sqldelight = { id = "app.cash.sqldelight", version.ref = "sqldelight" } # Version '4.3.1.3277' introduced some regressions in CI time (more than 2x slower), so make sure