Upgrade Kover to 0.7.5

This commit is contained in:
Benoit Marty
2024-01-09 10:49:42 +01:00
committed by Benoit Marty
parent 4f872b3189
commit b727312040
7 changed files with 210 additions and 164 deletions

View File

@@ -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:

View File

@@ -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

View File

@@ -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()

View File

@@ -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<Delete>("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<Test>().configureEach {
/* TODO
extensions.configure<KoverTaskExtension> {
val enabled = name.contains("debug", ignoreCase = true)
isDisabled.set(!enabled)
disabledForProject.set(!enabled)
}
*/
}
}
}

1
changelog.d/1782.misc Normal file
View File

@@ -0,0 +1 @@
Migrate to Kover 0.7.X

View File

@@ -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

View File

@@ -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" }