Skip to content

Switch expression 0% coverage #936

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

Closed
Tum4ik opened this issue Aug 26, 2020 · 30 comments · Fixed by #1006
Closed

Switch expression 0% coverage #936

Tum4ik opened this issue Aug 26, 2020 · 30 comments · Fixed by #1006
Labels
bug Something isn't working tenet-coverage Issue related to possible incorrect coverage with repro Issue with repro

Comments

@Tum4ik
Copy link

Tum4ik commented Aug 26, 2020

I use coverlet.msbuild v2.9.0.
I use "dotnet test" task in Azure DevOps:
image

Somehow the execution of the switch expression is skipped, but that is impossible. The switch expression is executed for sure in my code flow.

image

However ReSharper shows the correct coverage.
image

@MarcoRossignoli
Copy link
Collaborator

Thanks for reporting this, seems related to new c# pattern matching.

@MarcoRossignoli MarcoRossignoli added breaking-change Issue or PR that represents a breaking change in features or functional. tenet-coverage Issue related to possible incorrect coverage untriaged To be investigated and removed breaking-change Issue or PR that represents a breaking change in features or functional. labels Aug 27, 2020
@Malivil
Copy link

Malivil commented Aug 28, 2020

It's definitely related to the new switch pattern. I have that in a few places in my code too running the same coverlet.msbuild version

@MarcoRossignoli MarcoRossignoli added the bug Something isn't working label Aug 29, 2020
@wdolek
Copy link
Contributor

wdolek commented Sep 10, 2020

@MarcoRossignoli Since my code suffers from this issue I decided to look at it. I started with additional method in Samples.cs and test for it, but I'm bit puzzled by what IL I see. For reason I don't understand, I'm getting two additional branches before and after switch itself - ending up with total 8 branch points.

I'm not getting that on Sharplab nor when I write code in LINQPad or in another assembly. As you can see from IL, expression switch shouldn't be different from regular switch.

Do you have any clue why this is happening? What am I missing?

Branche code generated before/after switch:

IL_0000: nop
IL_0001: ldc.i4.1
IL_0002: brtrue.s IL_0005 // <-- wat, why?!
IL_0004: nop
IL_0005: ldarg.1
IL_0006: ldc.i4.1
IL_0007: sub
IL_0008: switch IL_001b,IL_001f,IL_0027
...

@MarcoRossignoli
Copy link
Collaborator

Can you try in release to see if it disappears?that branch could be needed for debuggers, for instance to put on it a breakpoint

@wdolek
Copy link
Contributor

wdolek commented Sep 11, 2020

@MarcoRossignoli You are right, I'm not getting those conditions in Release build. Also I think I might have some compiler options on, so that would explain why I didn't see it there before.

Anyway, I grabbed instrumented copy of method with switch expression and I do see instrumentation hits there just fine.

IL_0028: ldarg.1
IL_0029: ldc.i4.1
IL_002a: sub
IL_002b: switch (IL_003e, IL_0050, IL_0066)
IL_003c: br.s IL_007c
IL_003e: ldc.i4.s 53
IL_0040: call void Coverlet.Core.Instrumentation.Tracker.test__coverlet_instrumented::RecordHit(int32)
IL_0045: ldc.i4.s 54
IL_0047: call void Coverlet.Core.Instrumentation.Tracker.test__coverlet_instrumented::RecordHit(int32)
IL_004c: ldc.i4.m1
IL_004d: stloc.0
IL_004e: br.s IL_008e

Problem is not in instrumentation itself (luckily, because it is still good ol' switch - nothing broken here). At the moment, I'm not sure where to look next - will be glad for any input, or happily leave issue to someone experienced.

@MarcoRossignoli
Copy link
Collaborator

You should try to add a tmp test like https://github.com/coverlet-coverage/coverlet/blob/master/test/coverlet.core.tests/Coverage/CoverageTests.SelectionStatements.cs#L14 and use FunctionExecutor.RunInProcess instead of FunctionExecutor.Run, with your sample you can live debug all instumentation and at the end you can only show like reportgenerator outcome here https://github.com/coverlet-coverage/coverlet/blob/master/test/coverlet.core.tests/Coverage/CoverageTests.SelectionStatements.cs#L40 with extension result.GenerateReport(show:true)

Usually the two main point to check is branch getter https://github.com/coverlet-coverage/coverlet/blob/master/src/coverlet.core/Instrumentation/Instrumenter.cs#L540
And after understand if code is passed over instrumented code(i.e. IL_003e: ldc.i4.s 53 hit number 53) live checking this method https://github.com/coverlet-coverage/coverlet/blob/master/src/coverlet.core/Coverage.cs#L372 where we load hits file(raw file with line/hits count mapping). Sometime the issue is that we remove wrong branch/piece of code for bad pattern recognition.

@wdolek
Copy link
Contributor

wdolek commented Sep 15, 2020

@MarcoRossignoli I spent some time trying to understand what's going on and I have to give up (for now).

What I tried:

As I wrote before, I can see switch between two ifs (in Debug). If I was following code correctly, Coverage.CalculateCoverage behaves somehow expectable - except it gets 0 hits from preceding if and marks everything not reached because that if spans over exact same lines as switch.

I redirected my attention back to instrumenter to check branch points and line ranges. Branch points, at least regarding preceding if, look fine, however I'm not sure with sequence point afterwards - and that's where I ended.

Since this domain is completely new for me, I'm honestly not even sure if my observation is correct - so for anyone else looking into this issue, don't take my findings for granted.

@MarcoRossignoli
Copy link
Collaborator

@wdolek every idea/thought is welcome and will help everyone will move on this, thanks a lot for your effort!I'm bit busy but I'll take a look asap and I'll start from your analysis, so many many thanks!

@costin-zaharia-sonarsource

We hit this problem also on our side. It can be easily reproducible:

public static int Compute(object x)
{
    return x switch
    {
        int number => number,
        string value => value.Length,
        _ => -1
    };
}

@wdolek
Copy link
Contributor

wdolek commented Sep 18, 2020

Not that it changes anything, just be aware that those short switches are actually compiled to bunch of simple ifs - still, in Debug, all that is sandwiched between empty ifs, where one spans across whole method - which is weird.

@MarcoRossignoli I'm looking forward to see what's wrong here 🤞

@MarcoRossignoli MarcoRossignoli removed the untriaged To be investigated label Sep 18, 2020
@MarcoRossignoli
Copy link
Collaborator

other repro danielpalme/ReportGenerator#362 (comment)

@MarcoRossignoli
Copy link
Collaborator

After some investigation I found the issue is similar to lambda closure, compiler for new switch pattern emits overlapped sequence point, so we need to understand how to do correct accounting

image

@fahadabdulaziz
Copy link

Hello,
Is there any estimate when this issue cloud be resolved or at least workaround.

Thanks

@MarcoRossignoli
Copy link
Collaborator

MarcoRossignoli commented Oct 12, 2020

I don't have ETA but I have some ideas on how to solve that it's better than nothing 😄 I'll try to fix asap!
I apologize for the delay.

at least workaround.

Unfortunately there is no workaround is related to the way C# compiler emits sequence for new pattern, we have to fix in code.

@fahadabdulaziz
Copy link

Thank you , hope you all the best 👍

@daveMueller
Copy link
Collaborator

@MarcoRossignoli Can I help here somehow? I just took a peek at the IL and see an additional branch point and a sequence point that spans over multiple lines. I guess we could try to find a match for the switch pattern and ignore the branch point. Still this doesn't seem to be enough.

@MarcoRossignoli
Copy link
Collaborator

MarcoRossignoli commented Nov 3, 2020

My idea was bit different, we could count in different way here ~ https://github.com/coverlet-coverage/coverlet/blob/master/src/coverlet.core/Coverage.cs#L384

We could create some structure to keep the actual accounted line for every sequence, try to explain better, suppose to have 3 overlapped sequences record similar to nested closure or switch:

1 sequence start line 10 end line 18 -> 10 hit
2 sequence start line 13 end line 17 -> 5 hit
3 sequence start line 14 end line 16 -> 0 hit

We could build some metadata for every range sequence where we report the "line to not account" because is accounted by a nested sequence and my statement is that the most nested sequence range is the right one. So in the case above we could end with something like

line = hits count

10 = 10
11 = 10
12 = 10
13 = 5
14 = 0
15 = 0
16 = 0
17 = 5
18 = 10

Are you able to try this alg? The changes should be done here https://github.com/coverlet-coverage/coverlet/blob/master/src/coverlet.core/Coverage.cs#L384-L444
In this way we should fix one for all the "overlapped" sequence(lines) accounting issues.

This month I'm very busy so I think I won't have enough time until next month, I was experimenting here master...MarcoRossignoli:issue_936_switch take my test if you want(ignore the patch code doesn't work as-is is only a test I was in debugging).
Could be great fix this issue...in my plan it's the last thing to fix before 3.0.0 release because a lot of people are using new switch pattern.
@daveMueller if you need further assistance we can also have a talk/call on skype.

@daveMueller
Copy link
Collaborator

Yes OK I got it. I will try this and get back to you when I'm facing some issues.

@daveMueller
Copy link
Collaborator

I rapidly prototyped this and at a first glimpse it seemed to work out.

image

Then I tested a bit with value and switch in separate rows and it immediately failed.

image

What was bugging me all the time when looking at the IL was this first branch point that seems to be some internal null check or so. And I'm pretty sure that this is the problem. Because of that our instrumentation instruction for the 2nd sequence point is never hit. This can be seen in the screenshot of the IL that was posted before. Just to make it a bit more clear here again.

image

I'm still trying to figure out a solution for that but every idea/thought on this is very welcome.

@MarcoRossignoli
Copy link
Collaborator

Can you share your test branch so I can clone and debug?

Btw the switch coverage seems good, right? I mean the case are correctly reported?

Does it work with all other tests?

We can try to release a first version with the "correct branch coverage" because it's not so important that last sequence (for the moment). And after try to skip compiler injected branches.

Can you try to run in release?

@daveMueller
Copy link
Collaborator

daveMueller commented Nov 8, 2020

Can you share your test branch so I can clone and debug?

Sure but this is really early stage (https://github.com/daveMueller/coverlet/tree/936_SwitchExpression).

Btw the switch coverage seems good, right? I mean the case are correctly reported?

Yes cases are correctly reported.

Does it work with all other tests?

Up to now I didn't test too much. I don't know if I broke something and if the other unit tests are still green.

We can try to release a first version with the "correct branch coverage" because it's not so important that last sequence (for the moment). And after try to skip compiler injected branches.

See next answer.

Can you try to run in release?

I checked the IL of the release and this compiler injected branchpoint is gone. Now the result of the implementation is also looking good. 😏

image

Is it a requirement that coverlet also reports correctly for debug builds? If not I could ignore the problem I'm facing with the debug builds.

@MarcoRossignoli
Copy link
Collaborator

Is it a requirement that coverlet also reports correctly for debug builds? If not I could ignore the problem I'm facing with the debug builds.

Usually I ask to do coverage in debug because pdb is more precise/rich but with it there are also more false/true positive/negative. So my opinion here for now is that it's hard have a very precise % on coverage and usually a dev should check % but also take a look at report of branches/lines to understand the needs of more tests. In case of switch I prefer a correct branch cases coverage that a false negative on a bracket. Also because there are some place where is very very hard recognize correctly the pattern and so a false negative of 1 line shouldn't be a problem on % number.

I'll go for this fix for now and after wait for feedbacks.

Run a complete test and let me know!

Thanks a lot for the effort!

@daveMueller
Copy link
Collaborator

I got a question regarding coverage of a simple if statement. Has the line coverage for the else keyword always been 'Not coverable'?

image

I ask this because with my current implementation the test Lambda_Issue343 fails. The test requires the else keyword to be hit. I personally think that the test passed before, because the hit of the else keyword was just a result of the overlapping sequence points. I didn't confirm this yet but I'm pretty sure that's the reason that it now fails.

@MarcoRossignoli
Copy link
Collaborator

If it expected in test i think it was hit, you should try to understand why, I don't remember overlapped sequences but my memory could fail. Thanks a lot for the effort Dave.

@MarcoRossignoli
Copy link
Collaborator

MarcoRossignoli commented Nov 18, 2020

@daveMueller some more info on this, we can skip generated sequences/branches looking for this pattern https://github.com/dotnet/roslyn/blob/master/docs/compilers/CSharp/Expression%20Breakpoints.md
Thanks to @jakubch1 for the hint.

@MarcoRossignoli
Copy link
Collaborator

@daveMueller how is going with this?Let me know if you're busy so I can go on based on your idea and Jakub hint to skip first branche.

@daveMueller
Copy link
Collaborator

@MarcoRossignoli sorry was a bit busy lately on work. I've prepared PR today but then I've seen your PR and I also think this is the best and quickest solution. I add my thoughts to your PR.

@fahadabdulaziz
Copy link

@MarcoRossignoli I see this issue is fixed, when do you think we can get the update?

Thank you

@MarcoRossignoli
Copy link
Collaborator

Asap, I would try to fix one last issue in record, but if won't work we plan to release before end of year

@fahadabdulaziz
Copy link

Thank you.. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working tenet-coverage Issue related to possible incorrect coverage with repro Issue with repro
Projects
None yet
Development

Successfully merging a pull request may close this issue.

7 participants