coverlet
coverlet copied to clipboard
[BUG] Records show no coverage
Describe the bug With .Net 8, records show no coverage Tested with the nightly build too I read #1561 #1607 #1576
To Reproduce
public sealed record FolderInfoDto
{
public bool IsHidden { get; init; }
public long Creation { get; init; }
}
return new FolderInfoDto
{
Creation = f.Creation,
IsHidden = !string.IsNullOrEmpty(f.Mode) && 'h' == f.Mode[3]
};
internal abstract record BasicFolderDto
{
public string? DfsMappedPath { get; init; }
public string Path { get; init; } = null!;
}
internal sealed record CreateFolderDto : BasicFolderDto;
CreateFolderDto dto = new()
{
Path = args != null && args.TryGetValue("path", out var path) ? (string)path : @"F:\rep1\rep2",
};
internal sealed record DeleteFolderDto : BasicFolderDto
{
public bool Recurse { get; init; }
public bool Force { get; init; }
}
DeleteFolderDto dto = new()
{
Path = @"L:\path\",
};
Expected behavior
FolderInfoDto, CreateFolderDto, DeleteFolderDto, BasicFolderDto are marked as covered.
Actual behavior They are marked as not covered.
Configuration (please complete the following information): Please provide more information on your .NET configuration: * Which coverlet package and version was used? 6.0.1 and nightly * Which version of .NET is the code running on? 8.0.2 * What OS and version, and what distro if applicable? Windows 11 * What is the architecture (x64, x86, ARM, ARM64)? x64 * Do you know whether it is specific to that configuration? No
Additional context
- Same result from Visual Studio and CLI
- No difference with either
<TargetFramework>net8.0</TargetFramework>or<TargetFramework>net8.0-windows10.0.17763.0</TargetFramework> - removing
abstractorsealedmakes no difference - SkipAutoProps makes no difference
test.runsettings:
<?xml version="1.0" encoding="utf-8"?>
<RunSettings>
<RunConfiguration>
<ResultsDirectory>.\TestResults</ResultsDirectory>
<TargetPlatform>x64</TargetPlatform>
<TargetFramework>net8.0</TargetFramework>
</RunConfiguration>
<DataCollectionRunSettings>
<DataCollectors>
<DataCollector friendlyName="XPlat code coverage">
<Configuration>
<Format>cobertura</Format>
<Exclude>[coverlet.*.tests?]*,[*]Coverlet.Core*,[*]xunit*</Exclude>
</Configuration>
</DataCollector>
</DataCollectors>
</DataCollectionRunSettings>
</RunSettings>
:exclamation: Please also read Known Issues
Thanks for reporting and providing a repro. I see if I can get the discussion in #1576 started. I think we then would have less issues with records.
Our SonarQube would not allow my PR since coverage was subpar. I was just refactoring dtos . I had to [ExcludeFromCodeCoverage] for the time being
Could you elaborate a bit on how to reproduce this? With my setup I can get coverage for those records.
Hello !
Latest SonarQube expects all the lines to be covered https://community.sonarsource.com/t/sonarqube-not-showing-coverage-for-inherited-record/107476 I'm not sure whether they should be marked as covered or not
A hopefully useful additional data point
- Visual Studio 2022 17.10.0
- FineCodeCoverage 1.1.215
- NUnit 4.1.0
- coverlet.collector 6.0.2
- C# 12
- .NET 8
I have an internal record:
internal record Foo
{
public string Bar() => "baz";
}
And an associated test project with a test:
[TestFixture]
internal class FooTests
{
[Test]
public void FooBarShouldReturnBaz()
{
var foo = new Foo();
Assert.That(foo.Bar(), Is.EqualTo("baz"));
}
}
After running the test, the method call shows that it was covered, but the record itself does not:
If I modify the record declaration to include the redundant primary constructor parentheses:
internal record Foo()
{
public string Bar() => "baz";
}
and repeat the test, this time I get green all around:
I confirm Thanks @Steve-OH !
I have similar issue, it gets fixed if i remove my constructor and use the parenthesis in record declaration. Here's an example:
Note that constructor without coverage seems to accept an argument of the same type.
And here it's gone:
Hello !
Latest SonarQube expects all the lines to be covered https://community.sonarsource.com/t/sonarqube-not-showing-coverage-for-inherited-record/107476 I'm not sure whether they should be marked as covered or not
OK I see. It looks like SonarQube is expecting the record declaration as something that should be covered. This is something we are not doing. If you ignore all the generated IL of a record it is just the same as a simple class declaration. Is SonarQube expecting coverage for a class declaration? I don't think so because it doesn't really have any executable code.
@Steve-OH, @Rast1234 I think I now found one of the issues here. Starting from the sample code @Steve-OH provided. If I add the primary constructor parentheses, the generated report contains the constructor. This is an issue we need to tackle.
If it doesn't contain the parentheses the constructor is not part of the coverage report. Which should be the expected report in both cases because we want generated constructors to be completely excluded.
But in your sample, the record declaration seems to be expected to be covered also without the parentheses. This is different to what I'm currently observing. I wonder if this is part of the report? Are you able to provide me your coverage reports for both cases?
