-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Calculate line coverage for Kotlin inline functions #1670
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@marchof @leveretka IMO this requires review from both of you 😉 |
| * Parsed representation of SourceDebugExtension attribute. | ||
| */ | ||
| final class KotlinSMAP { | ||
| public final class KotlinSMAP { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still Kotlin specific? Or should we move it to org.jacoco.core.internal.analysis.SMAP? This would resolve the dependency from the Analyzer on a Kotlin specific class.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this still Kotlin specific?
@marchof yes - it is still Kotlin specific.
|
@Godin I would like to draw some diagrams for my better understanding. Sorry for some stupid questions:
|
|
@marchof take your time to review and no worries - your questions usually lead to improvements 😉 SMAP of classfile contains list of Note that there is also mapping of class with SMAP on itself - see condition Currently In this PR in accordance with https://www.jacoco.org/jacoco/trunk/doc/counters.html
result is stored inside current Then later, after all the classes were observed, After that as before So that at the end we receive updated counters on all levels - bundle, package, class, method, source, line. Also in this PR Hope I understood well your current questions and the above answers them and clarifies things. |
|
@Godin I created a description of our current coverage model: https://github.com/jacoco/jacoco/wiki/CoverageDataModel I will create another document describing the additional requirements for inlining. |
|
@Godin @leveretka Out of curiosity: How do debuggers deal with inline functions? |
|
@Godin Thanks for your detailed explanation. I try to write some details here: https://github.com/jacoco/jacoco/wiki/CoverageDateModelForInlining So SMAP maps line numbers which in turn are probably mapped to instructions with the regular LineNumberTable, right? So I try to understand some corner cases:
|
AFAIK debugger in IntelliJ IDEA uses SMAPs and additional information that we do not - we use only
Not probably - for sure 😉 see condition in
Seems that you miss an important point: Inlined instructions receive generated line numbers greater than originally presented in source and SMAP provides mappings for these generated line numbers. We implemented And as far as I can see different inlines, even of the same original function, receive different generated line numbers. So yes - in this case we can tell which instructions belong to which original method. For example for and execution of produces
And execution of produces following report BTW our setup for validation tests provides nice way to do experiments 😉
Indeed. In this case line coverage will be correct with respect to its definition, but not method coverage. BTW https://github.com/JetBrains/intellij-coverage has the same limitation: Maybe this can be improved using more information like debugger does, but there are issues such as https://youtrack.jetbrains.com/issue/KT-60276/Support-debugging-inline-functions-on-Android And IMO highly unlikely that functions in non-generated code will be defined on the same line. So IMO not worth it and this is acceptable limitation, at least right now. Let me also introduce/CC @zuevmaxim from JetBrains who AFAIK works on intellij-coverage, debugger and JaCoCo integration 🖖 Also let me refer to "perfection is the enemy of progress" and "perfect is the enemy of good" here and 80/20 Pareto principle in a sense that IMO efforts to deal with this corner case are much higher than benefits 😉 |
I looked at some popular open-source projects, and the results matched expectations and our validation tests. For example for https://github.com/detekt/detekt
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's nice to see that this subject is moving. It was a long-awaited enhancement in the Kotlin community. And we're looking forward to the next steps.
From my side, everything looks fine. The results look indeed impressive. Thanks @Godin for taking care of this!
I wanted to check if @marchof has any concerns before giving my approval.
|
@Godin @leveretka Thanks for moving forward here! Unfortunately my day job did not allow me to spend enough time here to fully understand the approach. To not block this I propose that we merge this and fix/cleanup things that we discover later. I have one question though that bugs me: If I understand correctly, the inlined code is analyzed separately and then counters are merged later. How is merging instructions and branches possible based on counters? Let's say two call-sites both have a branch counter of 1/1. Depending on whether the same branch or a different branch was executed in both call-sites the combined coverage is 1/1 or 0/2. I would have expected that proper merging coverage for inline functions would require knowledge of the execution status of every branch and instruction. Something like we have today for filters ( |
|
@marchof regarding
you're right, and in order to step-by-step progress on this not-so-small subject, I explicitly split it and among other items branch coverage was explicitly excluded and this PR focuses only on line coverage as stated in its title and description
as well as in the entry added to the changelog. Also IMO from a Kotlin user point of view, even without branch coverage the ability to see line coverage for inline functions is quite a significant improvement on its own - recall that JaCoCo was not reporting branch coverage at all till version 0.5.0 😉 |
|
@Godin Thanks for the explanation! While digging into the implementation details I obviously totally missed the objective in the headline 🙈. So please move forward with this! |
After recent #1525 and #1663, for resolution of #654 I propose to do another baby step, which IMO is quite significant from a Kotlin user point of view.
The following items can be improved later, so were deliberately excluded from this change
For
src/main/kotlin/Example.ktand
src/test/kotlin/ExampleTest.ktfrom example.zip
execution of
mvn verifyas well asgradle test jacocoTestReportbefore this change produces
and after this change produces
Fixes #654