Kotlin / kotlinx-kover

Apache License 2.0
1.33k stars 52 forks source link

kover should able to exclude interface default method #623

Open hanrw opened 3 months ago

hanrw commented 3 months ago

It's nice to have a filter for excluding the interface which has a default implementation E.g

package org.koin.core.component

import org.koin.core.Koin
import org.koin.core.parameter.ParametersDefinition
import org.koin.core.qualifier.Qualifier
import org.koin.mp.KoinPlatformTools

/**
 * KoinComponent interface marker to bring Koin extensions features
 *
 * @author Arnaud Giuliani
 */
interface KoinComponent {

    /**
     * Get the associated Koin instance
     */
    fun getKoin(): Koin = KoinPlatformTools.defaultContext().get()
}

data class AnthropicConfig(
    val apiKey: () -> String = { "CONFIG_API_KEY" },
    val baseUrl: () -> String = { Anthropic.BASE_URL },
    val anthropicVersion: () -> String = { Anthropic.ANTHROPIC_VERSION },
) : KoinComponent

Check here

image

the cover is not covered unless added a ut like below

class AnthropicConfigTest : AutoCloseKoinTest() {

    @Test
    fun `only fix the coverage by this case`() {
        startKoin {
            val r = AnthropicConfig().getKoin()
            assertNotNull(r)
        }
    }
}
shanshin commented 3 months ago

We will consider adding similar filters in the future.

But could you describe your case in more detail, for what purpose do you have methods with default implementation in interfaces, but it is acceptable for you not to use them?

hanrw commented 3 months ago

I discovered the issue after introducing Koin(https://github.com/InsertKoinIO/koin) as the dependency injection framework in my project. and found the interface KoinComponent has a default implementation.

shanshin commented 3 months ago

Do I understand correctly that the KoinComponent interface does not belong to your project and you did not write it? In this case, you should exclude it from the reports.

The collection of coverage is primarily aimed at finding unused code that the project developer wrote. If any external dependencies are appeared in the report, they should be completely exclude (because the code from external dependencies is not controlled by the developer).

hanrw commented 3 months ago

yes. you are right. but i have my own code which extends external code like KoinComponent for this case is there better solution from your point of view? let's say for the class AnthropicConfig I had covered call cases but the kover report that there's something that not cover yet.

shanshin commented 3 months ago

let's say for the class AnthropicConfig I had covered call cases but the kover report that there's something that not cover yet.

could you provide an XML report, at least part with class AnthropicConfig coverage?

The report you provided is generated using codecov, we do not control it directly, it may have its own rules for calculating coverage

hanrw commented 3 months ago

Here you go build.gradle.kts

kover {
    reports {
        filters {
            excludes {
                classes(
                    "com.tddworks.**.*\$*$*", // Lambda functions like - LemonSqueezyLicenseApi$activeLicense$activationResult$1
                    "com.tddworks.**.*\$Companion", // Lambda functions like - LemonSqueezyLicenseApi$activeLicense$activationResult$1
                    "*.*\$\$serializer", // Kotlinx serializer)
                )
//            inheritedFrom("org.koin.core.component.KoinComponent")
//            annotatedBy("kotlinx.serialization.Serializable")
            }
            includes {
                classes("com.tddworks.*")
            }
        }

        verify {
            rule {
                bound {
                    minValue = 82
                }
            }
        }
    }
}

report xml

<package name="com/tddworks/anthropic/api">
<class name="com/tddworks/anthropic/api/AnthropicConfig" sourcefilename="AnthropicConfig.kt">
<method name="getKoin" desc="()Lorg/koin/core/Koin;">
<counter type="INSTRUCTION" missed="2" covered="0"/>
<counter type="BRANCH" missed="0" covered="0"/>
<counter type="LINE" missed="1" covered="0"/>
</method>
<method name="&lt;init>" desc="(Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)V">
<counter type="INSTRUCTION" missed="0" covered="24"/>
<counter type="BRANCH" missed="0" covered="0"/>
<counter type="LINE" missed="0" covered="3"/>
</method>
<counter type="INSTRUCTION" missed="2" covered="24"/>
<counter type="BRANCH" missed="0" covered="0"/>
<counter type="LINE" missed="1" covered="3"/>
<counter type="METHOD" missed="1" covered="1"/>
</class>

and you can also found from - app.codecov.io

Thanks.

shanshin commented 3 months ago

It's bug in Kover reporter.

Reproducer


interface Parent {
    fun getFoo(): String = "foo"
}

data class Child(
    val s: () -> String = { "Text" },
) : Parent

report

<class name="org/jetbrains/kover/android/test/Child" sourcefilename="Extensions.kt">
<method name="&lt;init&gt;" desc="(Lkotlin/jvm/functions/Function0;)V">
<counter type="INSTRUCTION" missed="8" covered="0"/>
<counter type="BRANCH" missed="0" covered="0"/>
<counter type="LINE" missed="1" covered="0"/>
</method>
<method name="getFoo" desc="()Ljava/lang/String;">
<counter type="INSTRUCTION" missed="2" covered="0"/>
<counter type="BRANCH" missed="0" covered="0"/>
<counter type="LINE" missed="1" covered="0"/>
</method>
<counter type="INSTRUCTION" missed="10" covered="0"/>
<counter type="BRANCH" missed="0" covered="0"/>
<counter type="LINE" missed="2" covered="0"/>
<counter type="METHOD" missed="2" covered="0"/>
</class>

Kotlin 1.9.23 and 2.0.0, intellij reporter 1.0.752

hanrw commented 3 months ago

thx.