Skip to content

Coverage is assigned to the wrong method when source methods share a matching key #12

Description

@pavelsem

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:

  1. 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.
  2. 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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions