Skip to content

Skip fsharp construct with unknown source #1165

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Documentation/Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased

### Fixed

-Fix F# projects with `unkown` source [#1145](https://github.com/coverlet-coverage/coverlet/issues/1145)
-Fix SkipAutoProps for inline assigned properties [#1139](https://github.com/coverlet-coverage/coverlet/issues/1139)
-Fix partially covered throw statement [#1144](https://github.com/coverlet-coverage/coverlet/pull/1144)
-Fix coverage threshold not failing when no coverage [#1115](https://github.com/coverlet-coverage/coverlet/pull/1115)
Expand Down
7 changes: 7 additions & 0 deletions coverlet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9A8B19D4
test\Directory.Build.targets = test\Directory.Build.targets
EndProjectSection
EndProject
Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "coverlet.tests.projectsample.fsharp", "test\coverlet.tests.projectsample.fsharp\coverlet.tests.projectsample.fsharp.fsproj", "{1CBF6966-2A67-4D2C-8598-D174B83072F4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -115,6 +117,10 @@ Global
{F8199E19-FA9A-4559-9101-CAD7028121B4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F8199E19-FA9A-4559-9101-CAD7028121B4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{F8199E19-FA9A-4559-9101-CAD7028121B4}.Release|Any CPU.Build.0 = Release|Any CPU
{1CBF6966-2A67-4D2C-8598-D174B83072F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CBF6966-2A67-4D2C-8598-D174B83072F4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CBF6966-2A67-4D2C-8598-D174B83072F4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CBF6966-2A67-4D2C-8598-D174B83072F4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -135,6 +141,7 @@ Global
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{F8199E19-FA9A-4559-9101-CAD7028121B4} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{9A8B19D4-4A24-4217-AEFE-159B68F029A1} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{1CBF6966-2A67-4D2C-8598-D174B83072F4} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9CA57C02-97B0-4C38-A027-EA61E8741F10}
Expand Down
66 changes: 40 additions & 26 deletions src/coverlet.core/Helpers/InstrumentationHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,13 @@ public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNot
using (MetadataReaderProvider embeddedMetadataProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry))
{
MetadataReader metadataReader = embeddedMetadataProvider.GetMetadataReader();
foreach (DocumentHandle docHandle in metadataReader.Documents)

var matchingResult = MatchDocumentsWithSources(metadataReader);

if (!matchingResult.allDocumentsMatch)
{
Document document = metadataReader.GetDocument(docHandle);
string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name));

// We verify all docs and return false if not all are present in local
// We could have false negative if doc is not a source
// Btw check for all possible extension could be weak approach
// We exlude from the check the autogenerated source file(i.e. source generators)
if (!_fileSystem.Exists(docName) && !docName.EndsWith(".g.cs"))
{
firstNotFoundDocument = docName;
return false;
}
firstNotFoundDocument = matchingResult.notFoundDocument;
return false;
}
}
}
Expand Down Expand Up @@ -165,20 +158,13 @@ public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDoc
_logger.LogWarning($"{nameof(BadImageFormatException)} during MetadataReaderProvider.FromPortablePdbStream in InstrumentationHelper.PortablePdbHasLocalSource, unable to check if module has got local source.");
return true;
}
foreach (DocumentHandle docHandle in metadataReader.Documents)

var matchingResult = MatchDocumentsWithSources(metadataReader);

if (!matchingResult.allDocumentsMatch)
{
Document document = metadataReader.GetDocument(docHandle);
string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name));

// We verify all docs and return false if not all are present in local
// We could have false negative if doc is not a source
// Btw check for all possible extension could be weak approach
// We exlude from the check the autogenerated source file(i.e. source generators)
if (!_fileSystem.Exists(docName) && !docName.EndsWith(".g.cs"))
{
firstNotFoundDocument = docName;
return false;
}
firstNotFoundDocument = matchingResult.notFoundDocument;
return false;
}
}
}
Expand All @@ -187,6 +173,27 @@ public bool PortablePdbHasLocalSource(string module, out string firstNotFoundDoc
return true;
}

private (bool allDocumentsMatch, string notFoundDocument) MatchDocumentsWithSources(MetadataReader metadataReader)
{
foreach (DocumentHandle docHandle in metadataReader.Documents)
{
Document document = metadataReader.GetDocument(docHandle);
string docName = _sourceRootTranslator.ResolveFilePath(metadataReader.GetString(document.Name));
Guid languageGuid = metadataReader.GetGuid(document.Language);
// We verify all docs and return false if not all are present in local
// We could have false negative if doc is not a source
// Btw check for all possible extension could be weak approach
// We exlude from the check the autogenerated source file(i.e. source generators)
// We exclude special F# construct https://github.com/coverlet-coverage/coverlet/issues/1145
if (!_fileSystem.Exists(docName) && !docName.EndsWith(".g.cs") &&
!IsUnknownModuleInFSharpAssembly(languageGuid, docName))
{
return (false, docName);
}
}
return (true, string.Empty);
}

public void BackupOriginalModule(string module, string identifier)
{
var backupPath = GetBackupPath(module, identifier);
Expand Down Expand Up @@ -443,5 +450,12 @@ private bool IsAssembly(string filePath)
return false;
}
}

private bool IsUnknownModuleInFSharpAssembly(Guid languageGuid, string docName)
{
// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#document-table-0x30
return languageGuid.Equals(new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3"))
&& docName.EndsWith("unknown");
}
}
}
21 changes: 21 additions & 0 deletions test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Xunit;
using System.Collections.Generic;
using System.Linq;
using Castle.Core.Internal;
using Moq;
using Coverlet.Core.Abstractions;

Expand All @@ -29,6 +30,26 @@ public void TestGetDependenciesWithTestAssembly()
Assert.True(Array.Exists(modules, m => m == module));
}

[Fact]
public void EmbeddedPortablePDPHasLocalSource_DocumentDoesNotExist_ReturnsFalse()
{
var fileSystem = new Mock<FileSystem> {CallBase = true};
fileSystem.Setup(x => x.Exists(It.IsAny<string>())).Returns(false);

InstrumentationHelper instrumentationHelper =
new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), fileSystem.Object, new Mock<ILogger>().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock<ILogger>().Object, new FileSystem()));

Assert.False(instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, out string notFoundDocument));
Assert.False(notFoundDocument.IsNullOrEmpty());
}

[Fact]
public void EmbeddedPortablePDPHasLocalSource_AllDocumentsExist_ReturnsTrue()
{
Assert.True(_instrumentationHelper.PortablePdbHasLocalSource(typeof(InstrumentationHelperTests).Assembly.Location, out string notFoundDocument));
Assert.True(notFoundDocument.IsNullOrEmpty());
}

[Fact]
public void TestHasPdb()
{
Expand Down
18 changes: 18 additions & 0 deletions test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,24 @@ public void TestInstrument_MissingModule()
loggerMock.Verify(l => l.LogWarning(It.IsAny<string>()));
}

[Fact]
public void CanInstrumentFSharpAssemblyWithAnonymousRecord()
{
var loggerMock = new Mock<ILogger>();

string sample = Directory.GetFiles(Directory.GetCurrentDirectory(), "coverlet.tests.projectsample.fsharp.dll").First();
InstrumentationHelper instrumentationHelper =
new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock<ILogger>().Object,
new SourceRootTranslator(sample, new Mock<ILogger>().Object, new FileSystem()));

var instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_fsharp", new CoverageParameters(), loggerMock.Object, instrumentationHelper,
new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper());

Assert.True(instrumentationHelper.HasPdb(sample, out bool embedded));
Assert.False(embedded);
Assert.True(instrumenter.CanInstrument());
}

[Theory]
[InlineData("NotAMatch", new string[] { }, false)]
[InlineData("ExcludeFromCoverageAttribute", new string[] { }, true)]
Expand Down
1 change: 1 addition & 0 deletions test/coverlet.core.tests/coverlet.core.tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
<ProjectReference Include="$(RepoRoot)src\coverlet.msbuild.tasks\coverlet.msbuild.tasks.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.projectsample.empty\coverlet.tests.projectsample.empty.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.projectsample.excludedbyattribute\coverlet.tests.projectsample.excludedbyattribute.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.projectsample.fsharp\coverlet.tests.projectsample.fsharp.fsproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.core.tests.samples.netstandard\coverlet.core.tests.samples.netstandard.csproj" />
<ProjectReference Include="$(RepoRoot)test\coverlet.tests.xunit.extensions\coverlet.tests.xunit.extensions.csproj" />
</ItemGroup>
Expand Down
4 changes: 4 additions & 0 deletions test/coverlet.tests.projectsample.fsharp/Library.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
namespace coverlet.tests.projectsample.fsharp

module TestModule =
type Type1 = Option1 | Option2 of {| x: string |}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<IsTestProject>false</IsTestProject>
</PropertyGroup>

<ItemGroup>
<Compile Include="Library.fs" />
</ItemGroup>

</Project>