As I'm driving my little set of FxCop rules and associated helpers to a usable state, I'm looking in more detail at how the code coverage in the automated system/integration test is going, trying to drive that towards 100%. And as usual, the F# code generation is throwing up some interesting results.
Take this method (lightly refactored from last time) which starts at line 167 in the actual source:
Although my tests give a match for every case in the pattern -- and nCover shows a visit for every line from 168 to 176 (2 to 9), and the expression body of each branch of the
else, it also claims that a code point that spans from from line 168 col 5 to line 177 col 24 (everything from
then inclusive) is not visited.
Unfortunately, the IL generated for this method is not back-compilable to C# in Reflector (yes, another problem report submitted), so I can't use that to analyse what is going on in this particular case : I shall have to inject some diagnostic code into the rule itself to see what FxCop thinks is going on as a statement which spans those lines.
But this isn't the only mysterious bit of uncoverage I've found in F# code -- in the previous state of the method (as showcased here), it was the identifier
let result = match… that got the uncoverage, despite the named value being used as the final expression in the method.
In most cases, it is possible to refactor away such temporaries, and clear the spurious uncoverages that they give rise to; but, as this example shows, it is not always possible.
By recursively dumping
Statements in FxCop for this method (which inserted 2 lines in a routine above this one, thus displacing the line numbers), I get a lot of multiple hits on statements (same type, same source context range), but the only ones with the appropriate source context are
Block -> from 170 : 5 to 179 : 24 Nop -> from 170 : 5 to 179 : 24 Nop -> from 170 : 5 to 179 : 24 AssignmentStatement -> from 170 : 9 to 170 : 22 AssignmentStatement -> from 170 : 9 to 170 : 22 Branch -> from 170 : 9 to 170 : 22 Block -> from 170 : 9 to 170 : 22 Branch -> from 170 : 9 to 170 : 22 Block -> from 170 : 9 to 170 : 22 Branch -> from 170 : 9 to 170 : 22 Block -> from 171 : 27 to 171 : 35 ExpressionStatement -> from 171 : 27 to 171 : 35 Nop -> from 171 : 27 to 171 : 35 …
So it seems that first
Nop pair, with no other statement occupying the the same range, may be what it being detected as uncovered (unlike the second
Nop, which overlaps the immediately preceding
The equivalent IL looks like
which does indeed have a pair of
Nop opcodes at the start (lines 21 and 22).
So, that looks like another heuristic to add - removing unvisited code point records that correspond to nothing but a
The February 2010 CTP generates identical code here.