/* * Copyright (c) 2024 New Vector Ltd * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package extension import kotlinx.kover.gradle.plugin.dsl.KoverReportExtension import org.gradle.api.Action import org.gradle.api.Project import org.gradle.kotlin.dsl.apply val localAarProjects = listOf( ":libraries:rustsdk", ":libraries:textcomposer:lib" ) val excludedKoverSubProjects = 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", ) + localAarProjects private fun Project.koverReport(action: Action) { (this as org.gradle.api.plugins.ExtensionAware).extensions.configure("koverReport", action) } fun Project.setupKover() { // 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.*", // Test presenter "io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter", ) annotatedBy( "androidx.compose.ui.tooling.preview.Preview", "io.element.android.libraries.architecture.coverage.ExcludeFromCoverage", "io.element.android.libraries.designsystem.preview.PreviewsDayNight", "io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight", ) } } defaults { // add reports of both 'debug' and 'release' Android build variants to default reports mergeWith("gplayDebug") 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 = 70 // 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 = 80 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 } } } } } } fun Project.applyKoverPluginToAllSubProjects() = rootProject.allprojects { if (project.path !in localAarProjects) { apply(plugin = "org.jetbrains.kotlinx.kover") } } fun Project.koverDependencies() { project.rootProject.subprojects .filter { it.project.projectDir.resolve("build.gradle.kts").exists() } .map { it.path } .sorted() .filter { it !in excludedKoverSubProjects } .forEach { // println("Add $it to kover") dependencies.add("kover", project(it)) } }