Summary
The matcher can give one method's coverage to a different method. Two cases are known. Both remain after #4.
Case 1: the name-only pass counts coverage entries, but not source methods
The name-only pass matches when exactly one coverage entry has the name key. It does not check how many source methods have that name key. An overload that the exact pass already matched can therefore give its coverage to an unmatched sibling overload.
[Fact]
public void NameOnlyMatch_SharedBySeveralSourceMethods_Refuses()
{
var complexity = new[]
{
MakeComplexity("Foo", signature: "(int)"),
MakeComplexity("Foo", signature: "(string)")
};
var coverage = new[] { MakeCoverage("Foo", signature: "(System.Int32)", coverage: 0.6) };
var result = MethodCoverageMatcher.Match(complexity, coverage);
result.Methods[0].Coverage.Should().Be(0.6);
result.Methods[1].Coverage.Should().Be(0.0); // currently 0.6
}
Possible fix: use the same guard as the relaxed pass in #4. Match only when exactly one source method has the name key.
Risk: methods that now receive a sibling's coverage will change to 0.0. Their CRAP scores will increase.
Case 2: Coverlet writes one entry for Foo(int) and Foo<T>(int)
When a class declares Foo(int) and Foo<T>(int), Coverlet writes one <method> entry. The entry contains the lines of both methods.
Source:
public class Helper
{
public int Foo(int x) { if (x > 0) return 1; return 0; } // line 4
public int Foo<T>(int x) { if (x > 0) return 2; return 0; } // line 5
}
A test calls only Foo<string>, with both branches. Coverlet output:
<method name="Foo" signature="(System.Int32)" line-rate="0.5" branch-rate="0.5" complexity="4">
<lines>
<line number="4" hits="0" branch="True" condition-coverage="0% (0/2)">...</line>
<line number="5" hits="8" branch="True" condition-coverage="100% (2/2)">...</line>
</lines>
</method>
Result in crap4dotnet:
| Method |
Actual coverage |
Reported coverage |
Foo(int) |
0.0 |
0.5 (exact match to the combined entry) |
Foo<T>(int) |
1.0 |
0.0 (relaxed pass refuses: two source methods share the key) |
The method-level rates cannot be split. The <lines> element keeps line numbers, and MethodIdentity.LineNumber gives each source method's start line.
Possible fixes:
- Compute each method's coverage from the
<line> elements inside its source line range. This requires the Cobertura reader to parse <lines> and the matcher to know each method's line range.
- At minimum, emit a warning when one coverage entry matches the key of more than one source method, so the user knows those scores are unreliable.
Unknown: how often this case occurs in real code. It requires a generic and a non-generic method with the same name and the same parameter types in one class.
Summary
The matcher can give one method's coverage to a different method. Two cases are known. Both remain after #4.
Case 1: the name-only pass counts coverage entries, but not source methods
The name-only pass matches when exactly one coverage entry has the name key. It does not check how many source methods have that name key. An overload that the exact pass already matched can therefore give its coverage to an unmatched sibling overload.
Possible fix: use the same guard as the relaxed pass in #4. Match only when exactly one source method has the name key.
Risk: methods that now receive a sibling's coverage will change to 0.0. Their CRAP scores will increase.
Case 2: Coverlet writes one entry for
Foo(int)andFoo<T>(int)When a class declares
Foo(int)andFoo<T>(int), Coverlet writes one<method>entry. The entry contains the lines of both methods.Source:
A test calls only
Foo<string>, with both branches. Coverlet output:Result in crap4dotnet:
Foo(int)Foo<T>(int)The method-level rates cannot be split. The
<lines>element keeps line numbers, andMethodIdentity.LineNumbergives each source method's start line.Possible fixes:
<line>elements inside its source line range. This requires the Cobertura reader to parse<lines>and the matcher to know each method's line range.Unknown: how often this case occurs in real code. It requires a generic and a non-generic method with the same name and the same parameter types in one class.