Skip to content

Use Xunit.v3 (latest release) #1720

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 22 commits into from
Mar 27, 2025
Merged
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
f8d57ba
use xunit.v3 for unit tests
Bertk Jan 26, 2025
013feab
cleanup '#pragma warning disable'
Bertk Jan 26, 2025
78807f7
Update publish-coverage-results.yml
Bertk Jan 26, 2025
fcdd26d
enabling Microsoft Testing Platform
Bertk Feb 11, 2025
e567eea
Merge branch 'xunit.v3' of https://github.com/Bertk/coverlet into xun…
Bertk Feb 11, 2025
0447967
enabling Microsoft Testing Platform for all tests
Bertk Feb 11, 2025
bdbe03d
Revert "enabling Microsoft Testing Platform for all tests"
Bertk Feb 11, 2025
4f6a6d3
enable Microsoft Testing Platform for coverlet core tests
Bertk Feb 13, 2025
59c1678
remove filters handled by publish-coverage-results.yml
Bertk Feb 13, 2025
5666085
add test Execute_StateUnderTest_Success()
Bertk Feb 14, 2025
adf41b1
fix warning
Bertk Feb 14, 2025
eca5e8c
Update package versions to use property references
Bertk Feb 15, 2025
1e09a04
use Microsoft Testing Plaform for coverlet.msbuild.task.tests
Bertk Feb 17, 2025
d7d571d
Update projects to .NET 8 and improve CI configurations
Bertk Feb 20, 2025
b82996e
small documentation update
Bertk Feb 20, 2025
cf354b1
Update changelog, documentation and add helper script `build.sh`
Bertk Feb 21, 2025
8eb7855
disable Microsoft.Testing.Platform for coverlet.msbuild.tasks.tests
Bertk Feb 22, 2025
8ebd2cb
Update NuGet package versions in Directory.Packages.props
Bertk Mar 2, 2025
cea59f5
use property XunitV3Version
Bertk Mar 2, 2025
8d9e0db
use property XunitRunnerVisualstudioVersion
Bertk Mar 2, 2025
64635c0
incorporate review comments
Bertk Mar 25, 2025
40148c1
Merge branch 'xunit.v3' of https://github.com/Bertk/coverlet into xun…
Bertk Mar 25, 2025
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
49 changes: 29 additions & 20 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
@@ -5,24 +5,33 @@
</PropertyGroup>
<ItemGroup>
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.7.112" />
<GlobalPackageReference Include="Nerdbank.GitVersioning" Version="3.7.115" />
</ItemGroup>
<PropertyGroup>
<MicrosoftBuildVersion>17.13.9</MicrosoftBuildVersion>
<MicrosoftCodeAnalysisVersion>4.12.0</MicrosoftCodeAnalysisVersion>
<!-- Test Platform, .NET Test SDK and Object Model -->
<MicrosoftNETTestSdkVersion>17.13.0</MicrosoftNETTestSdkVersion>
<NugetPackageVersion>6.13.2</NugetPackageVersion>
<XunitV3Version>2.0.0</XunitV3Version>
<XunitRunnerVisualstudioVersion>3.0.2</XunitRunnerVisualstudioVersion>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="DotNetConfig" Version="1.2.0" />
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="8.0.0" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="17.12.6" />
<PackageVersion Include="Microsoft.Build.Framework" Version="17.12.6" />
<PackageVersion Include="Microsoft.Build.Utilities.Core" Version="$(MicrosoftBuildVersion)" />
<PackageVersion Include="Microsoft.Build.Framework" Version="$(MicrosoftBuildVersion)" />
<PackageVersion Include="Microsoft.Build.Locator" Version="1.7.8" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" />
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="$(MicrosoftCodeAnalysisVersion)" />
<PackageVersion Include="Microsoft.Extensions.DependencyModel" Version="8.0.2" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="8.0.1" />
<PackageVersion Include="Microsoft.Extensions.FileSystemGlobbing" Version="8.0.0" />
<!--For test TestInstrument_NetstandardAwareAssemblyResolver_PreserveCompilationContext-->
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.12.0" />
<PackageVersion Include="Microsoft.TestPlatform.ObjectModel" Version="17.12.0" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="$(MicrosoftNETTestSdkVersion)" />
<PackageVersion Include="Microsoft.TestPlatform.ObjectModel" Version="$(MicrosoftNETTestSdkVersion)" />
<!-- Microsoft.TestPlatform.ObjectModel has a dependency to NuGet.Frameworks with specific version -->
<!-- https://github.com/microsoft/vstest/blob/9a0c41811637edf4afe0e265e08fdd1cb18109ed/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj#L35-->
<!-- https://github.com/microsoft/vstest/blob/9a0c41811637edf4afe0e265e08fdd1cb18109ed/src/Microsoft.TestPlatform.ObjectModel/Microsoft.TestPlatform.ObjectModel.csproj#L36-->
<!-- wrong configuration will create "build\coverlet.msbuild.targets(72,5): error : Unable to read beyond the end of the stream." -->
<!--
vstest 17.5 version /scripts/build/TestPlatform.Dependencies.props
@@ -31,32 +40,32 @@
NuGetFrameworksVersion is defined here https://github.com/microsoft/vstest/blob/9a0c41811637edf4afe0e265e08fdd1cb18109ed/eng/Versions.props#L94C1-L94C1
-->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.3" />
<PackageVersion Include="NuGet.Frameworks" Version="6.12.1" />
<PackageVersion Include="NuGet.Packaging" Version="6.12.1" />
<PackageVersion Include="NuGet.Versioning" Version="6.12.1" />
<PackageVersion Include="NuGet.Frameworks" Version="$(NugetPackageVersion)" />
<PackageVersion Include="NuGet.Packaging" Version="$(NugetPackageVersion)" />
<PackageVersion Include="NuGet.Versioning" Version="$(NugetPackageVersion)" />
<PackageVersion Include="Mono.Cecil" Version="0.11.6" />
<PackageVersion Include="Moq" Version="4.20.72" />
<PackageVersion Include="ReportGenerator.Core" Version="5.3.11" />
<!--For test issue 809 https://github.com/coverlet-coverage/coverlet/issues/809-->
<PackageVersion Include="LinqKit.Microsoft.EntityFrameworkCore" Version="8.1.7" />
<PackageVersion Include="LinqKit.Microsoft.EntityFrameworkCore" Version="8.1.8" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-beta4.22272.1" />
<!--To test issue 1104 https://github.com/coverlet-coverage/coverlet/issues/1104-->
<!-- latest Tmds.ExecFunction package uses EnvDTE V17.8.37221 -->
<PackageVersion Include="Tmds.ExecFunction" Version="0.8.0" />
<PackageVersion Include="xunit.v3" Version="$(XunitV3Version)" />
<PackageVersion Include="xunit.runner.visualstudio" Version="$(XunitRunnerVisualstudioVersion)" />
<PackageVersion Include="System.Buffers" Version="4.6.0" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.Configuration.ConfigurationManager" Version="8.0.0" />
<PackageVersion Include="System.Linq.Async" Version="6.0.1" />
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.1" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.0" />
<PackageVersion Include="Tmds.ExecFunction" Version="0.8.0" />
<PackageVersion Include="xunit" Version="2.9.2" />
<PackageVersion Include="xunit.assemblyfixture" Version="2.2.0" />
<PackageVersion Include="xunit.assert" Version="2.9.2" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="System.Buffers" Version="4.6.0" />
<PackageVersion Include="System.Memory" Version="4.6.0" />
<PackageVersion Include="System.Net.Http" Version="4.3.4" />
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.1" />
<PackageVersion Include="System.Runtime.CompilerServices.Unsafe" Version="6.1.0" />
<PackageVersion Include="System.Security.Cryptography.Pkcs" Version="6.0.5" />
<PackageVersion Include="System.Text.Encoding.CodePages" Version="8.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.5" />
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.0" />
</ItemGroup>
</Project>
11 changes: 9 additions & 2 deletions Documentation/Changelog.md
Original file line number Diff line number Diff line change
@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Fixed
- use `<TargetFramework>netstandard2.0</TargetFramework>` for _coverlet.collector_ and _coverlet.msbuild.tasks_ Packages´

### Improvements
- use [xunit.v3](https://xunit.net/docs/getting-started/v3/whats-new) for tests and example code


## Release date 2024-01-20
### Packages
coverlet.msbuild 6.0.4
@@ -60,8 +67,8 @@ coverlet.collector 6.0.1
- Uncovered lines in .NET 8 for inheriting records [#1555](https://github.com/coverlet-coverage/coverlet/issues/1555)
- Fix record constructors not covered when SkipAutoProps is true [#1561](https://github.com/coverlet-coverage/coverlet/issues/1561)
- Fix .NET 7 Method Group branch coverage issue [#1447](https://github.com/coverlet-coverage/coverlet/issues/1447)
- Fix ExcludeFromCodeCoverage does not exclude method in a partial class [#1548](https://github.com/coverlet-coverage/coverlet/issues/1548)
- Fix ExcludeFromCodeCoverage does not exclude F# task [#1547](https://github.com/coverlet-coverage/coverlet/issues/1547)
- Fix ExcludeFromCodeCoverage does not exclude method in a partial class [#1548](https://github.com/coverlet-coverage/coverlet/issues/1548)
- Fix ExcludeFromCodeCoverage does not exclude F# task [#1547](https://github.com/coverlet-coverage/coverlet/issues/1547)
- Fix issues where ExcludeFromCodeCoverage ignored [#1431](https://github.com/coverlet-coverage/coverlet/issues/1431)
- Fix issues with ExcludeFromCodeCoverage attribute [#1484](https://github.com/coverlet-coverage/coverlet/issues/1484)
- Fix broken links in documentation [#1514](https://github.com/coverlet-coverage/coverlet/issues/1514)
6 changes: 3 additions & 3 deletions Documentation/ConsumeNightlyBuild.md
Original file line number Diff line number Diff line change
@@ -33,7 +33,7 @@ PM> Install-Package coverlet.msbuild -Version X.X.X-preview.X.XXX -Source https:
Example:

```powershell
PM> Install-Package coverlet.msbuild -Version 3.0.4-preview.4.g5de0ad7d60 -Source https://pkgs.dev.azure.com/tonerdo/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json
PM> Install-Package coverlet.msbuild -Version 6.0.4-preview.4.g5de0ad7d60 -Source https://pkgs.dev.azure.com/tonerdo/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json
```

### .NET CLI
@@ -45,7 +45,7 @@ PM> Install-Package coverlet.msbuild -Version 3.0.4-preview.4.g5de0ad7d60 -Sourc
Example:

```bash
dotnet add package coverlet.msbuild --version 3.0.4-preview.4.g5de0ad7d60 --source https://pkgs.dev.azure.com/tonerdo/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json
dotnet add package coverlet.msbuild --version 6.0.4-preview.4.g5de0ad7d60 --source https://pkgs.dev.azure.com/tonerdo/coverlet/_packaging/coverlet-nightly/nuget/v3/index.json
```

### MSBuild project file
@@ -57,5 +57,5 @@ Example:
Example:

```xml
<PackageReference Include="coverlet.msbuild" Version="3.0.4-preview.4.g5de0ad7d60" />
<PackageReference Include="coverlet.msbuild" Version="6.0.4-preview.4.g5de0ad7d60" />
```
4 changes: 2 additions & 2 deletions Documentation/DriversFeatures.md
Original file line number Diff line number Diff line change
@@ -8,8 +8,8 @@ In the table below we keep track of main differences:

| Feature | MSBuild | .NET Tool | DataCollectors |
|:-----------------------------------|:--------------|--------------|------------------|
| .NET Core support(>= 2.0) | Yes | Yes | Yes |
| .NET Framework support(>= 4.6.1) | Yes | Yes | Yes(since 3.0.0) |
| .NET Core support(>= 6.0) | Yes | Yes | Yes |
| .NET Framework support(>= 4.7.2) | Yes | Yes | Yes(since 3.0.0) |
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are not supporting 4.6.1 anymore? And nothing < net core 6? I think we still can instrument the e.g. .NET Core 2.0 projects. But coverlet needs a .NET6 runtime to execute.

At least with the .NET Tool I think it should still be possible?

And can you tell/remind me why we have these constraints on the collector and msbuild drivers again. I see that they are both netstandard2.0. Is it because of the libraries they are using?

Copy link
Collaborator Author

@Bertk Bertk Mar 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Support for .NET Framework 4.5.2, 4.6, and 4.6.1 ended on April 26, 2022. Customers and developers must have completed the in-place update to .NET Framework 4.6.2 or later by April 26, 2022 to continue receiving technical support and security updates.

.NET Framework 4.7.2 is the first version which supports netstandard2.0 and we use nuget packages which require .NET 6.0 runtime. By the way, coverlet.collector.csproj file currently has only netstandard2.0 and we can change this in another PR.

see also current vstest collector example

example uses : 
<TargetFrameworks>$(NetFrameworkMinimum);$(NetCoreAppMinimum)</TargetFrameworks>
...
    <NetFrameworkMinimum>net462</NetFrameworkMinimum>
    <NetCoreAppMinimum>net8.0</NetCoreAppMinimum>

| Show result on console | Yes | Yes | No |
| Deterministic reports output folder| Yes | Yes | No |
| Merge reports | Yes | Yes | No |
8 changes: 4 additions & 4 deletions Documentation/Examples/MSBuild/DeterministicBuild/HowTo.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
To run test we need to generates packages to reference in on test project.
To run test we need to generates packages to reference in on test project.
Run from repo root

```shell
@@ -44,9 +44,9 @@ Add msbuild package version generated to `"..\Documentation\Examples\MSBuild\Det
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Original file line number Diff line number Diff line change
@@ -11,12 +11,12 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</PackageReference>
</ItemGroup>

<ItemGroup>
Original file line number Diff line number Diff line change
@@ -11,9 +11,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Original file line number Diff line number Diff line change
@@ -11,9 +11,9 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
6 changes: 3 additions & 3 deletions Documentation/Examples/VSTest/DeterministicBuild/HowTo.md
Original file line number Diff line number Diff line change
@@ -44,9 +44,9 @@ Add collectors package version generated to `"..\Documentation\Examples\VSTest\D
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Original file line number Diff line number Diff line change
@@ -7,9 +7,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
Original file line number Diff line number Diff line change
@@ -8,9 +8,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.6" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6">
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
65 changes: 34 additions & 31 deletions Documentation/GlobalTool.md
Original file line number Diff line number Diff line change
@@ -9,40 +9,43 @@ coverlet --help
The current options are (output of `coverlet --help`):

```text
Cross platform .NET Core code coverage tool 6.0.0.0
Description:
Cross platform .NET Core code coverage tool
Usage: coverlet [arguments] [options]
Usage:
coverlet.console <path> [options]
Arguments:
<ASSEMBLY|DIRECTORY> Path to the test assembly or application directory.
<path> Path to the test assembly or application directory.
Options:
-t|--target (REQUIRED) Path to the test runner application.
-a|--targetargs Arguments to be passed to the test runner.
-o|--output Output of the generated coverage report
-v|--verbosity Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed.
-f|--format Format of the generated coverage report. [default: json]
--threshold Exits with error if the coverage % is below value.
--threshold-type Coverage type to apply the threshold to.
--threshold-stat Coverage statistic used to enforce the threshold value. [default: Minimum]
--exclude Filter expressions to exclude specific modules and types.
--include Filter expressions to include only specific modules and types.
--exclude-by-file Glob patterns specifying source files to exclude.
--include-directory Include directories containing additional assemblies to be instrumented.
--exclude-by-attribute Attributes to exclude from code coverage.
--include-test-assembly Specifies whether to report code coverage of the test assembly.
--single-hit Specifies whether to limit code coverage hit reporting to a single hit for each location
--skipautoprops Neither track nor record auto-implemented properties.
--merge-with Path to existing coverage result to merge.
--use-source-link Specifies whether to use SourceLink URIs in place of file system paths.
--does-not-return-attribute Attributes that mark methods that do not return.
--exclude-assemblies-without-sources Specifies behaviour of heuristic to ignore assemblies with missing source documents.
--source-mapping-file Specifies the path to a SourceRootsMappings file.
--version Show version information
-?, -h, --help Show help and usage information
-t, --target <target> (REQUIRED) Path to the test runner application.
-a, --targetargs <targetargs> Arguments to be passed to the test runner.
-o, --output <output> Output of the generated coverage report
-v, --verbosity <Detailed|Minimal|Normal|Quiet> Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed. [default: Normal]
-f, --format <format> Format of the generated coverage report. [default: json]
--threshold <threshold> Exits with error if the coverage % is below value.
--threshold-type <branch|line|method> Coverage type to apply the threshold to. [default: line|branch|method]
--threshold-stat <Average|Minimum|Total> Coverage statistic used to enforce the threshold value. [default: Minimum]
--exclude <exclude> Filter expressions to exclude specific modules and types.
--include <include> Filter expressions to include only specific modules and types.
--exclude-by-file <exclude-by-file> Glob patterns specifying source files to exclude.
--include-directory <include-directory> Include directories containing additional assemblies to be instrumented.
--exclude-by-attribute <exclude-by-attribute> Attributes to exclude from code coverage.
--include-test-assembly Specifies whether to report code coverage of the test assembly.
--single-hit Specifies whether to limit code coverage hit reporting to a single hit for each location
--skipautoprops Neither track nor record auto-implemented properties.
--merge-with <merge-with> Path to existing coverage result to merge.
--use-source-link Specifies whether to use SourceLink URIs in place of file system paths.
--does-not-return-attribute <does-not-return-attribute> Attributes that mark methods that do not return
--exclude-assemblies-without-sources <exclude-assemblies-without-sources> Specifies behaviour of heuristic to ignore assemblies with missing source documents.
--source-mapping-file <source-mapping-file> Specifies the path to a SourceRootsMappings file.
--version Show version information
-?, -h, --help Show help and usage information
```

NB. For [multiple value] options you can either specify values multiple times i.e.
> [!NOTE]
> For [multiple value] options you can either specify values multiple times i.e.
```shell
--exclude-by-attribute 'Obsolete' --exclude-by-attribute 'GeneratedCode' --exclude-by-attribute 'CompilerGenerated'
@@ -74,10 +77,10 @@ _Note: The `--no-build` flag is specified so that the `/path/to/test-assembly.dl

Sometimes, there are tests that doesn't use regular unit test frameworks like xunit. You may find yourself in a situation where your tests are driven by a custom executable/script, which when run, could do anything from making API calls to driving Selenium.

As an example, suppose you have a folder `/integrationtest` which contains said executable (lets call it `runner.exe`) and everything it needs to successfully execute. You can use our tool to startup the executable and gather live coverage:
As an example, suppose you have a folder `/integrationtests` which contains said executable (lets call it `runner.exe`) and everything it needs to successfully execute. You can use our tool to startup the executable and gather live coverage:

```bash
coverlet "/integrationtest" --target "/application/runner.exe"
coverlet "/integrationtests" --target "/application/runner.exe"
```

Coverlet will first instrument all .NET assemblies within the `integrationtests` folder, after which it will execute `runner.exe`. Finally, at shutdown of your `runner.exe`, it will generate the coverage report. You can use all parameters available to customize the report generation. Coverage results will be generated once `runner.exe` exits. You can use all parameters available to customize the report generation.
@@ -106,7 +109,7 @@ The `--format` option can be specified multiple times to output multiple formats
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --format opencover --format lcov
```

By default, Coverlet will output the coverage results file(s) in the current working directory. The `--output` or `-o` options can be used to override this behaviour.
By default, Coverlet will output the coverage results file(s) in the current working directory. The `--output` or `-o` options can be used to override this behavior.

```bash
coverlet <ASSEMBLY> --target <TARGET> --targetargs <TARGETARGS> --output "/custom/path/result.json"
@@ -256,7 +259,7 @@ Coverlet has the ability to map the paths contained inside the debug sources int

The value for `--source-mapping-file` should be a file with each line being in the format `|path to map to=path in debug symbol`. For example to map the local checkout of a project `C:\git\coverlet` to project that was built with `<Deterministic>true</Deterministic>` which sets the sources to `/_/*` the following line must be in the mapping file.

```
```text
|C:\git\coverlet\=/_/
```

2 changes: 1 addition & 1 deletion Documentation/KnownIssues.md
Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ If you upgrade the collector package with a version greater than 1.0.0, in-proc
```xml
<ItemGroup>
...
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
...
</ItemGroup>
```
8 changes: 4 additions & 4 deletions Documentation/MSBuildIntegration.md
Original file line number Diff line number Diff line change
@@ -189,7 +189,7 @@ Examples
* `/p:Include="[coverlet.*]Coverlet.Core.Coverage"` => Includes the Coverage class in the `Coverlet.Core` namespace belonging to any assembly that matches `coverlet.*` (e.g `coverlet.core`)
* `/p:Include="[coverlet.*.tests?]*"` => Includes all types in any assembly starting with `coverlet.` and ending with `.test` or `.tests` (the `?` makes the `s` optional)

Both `Exclude` and `Include` properties can be used together but `Exclude` takes precedence. You can specify multiple filter expressions by separting them with a comma (`,`).
Both `Exclude` and `Include` properties can be used together but `Exclude` takes precedence. You can specify multiple filter expressions by separating them with a comma (`,`).

You can also include coverage of the test assembly itself by setting `/p:IncludeTestAssembly` to `true`.

@@ -198,7 +198,7 @@ You can also include coverage of the test assembly itself by setting `/p:Include
Neither track nor record auto-implemented properties.
Syntax: `/p:SkipAutoProps=true`

### Instrument module wihtout local sources file
### Instrument module without local sources file

Syntax: `/p:InstrumentModulesWithoutLocalSources=true`

@@ -218,7 +218,7 @@ To exclude or include multiple assemblies when using Powershell scripts or creat
Azure DevOps builds do not require double quotes to be unescaped:

```shell
dotnet test --configuration $(buildConfiguration) --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ /p:Exclude="[MyAppName.DebugHost]*%2c[MyAppNamet.WebHost]*%2c[MyAppName.App]*"
dotnet test --configuration $(buildConfiguration) --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=$(Build.SourcesDirectory)/TestResults/Coverage/ /p:Exclude="[MyAppName.DebugHost]*%2c[MyAppName.WebHost]*%2c[MyAppName.App]*"
```

### Note for Linux users
@@ -258,7 +258,7 @@ This parameter has three different values to control the automatic assembly excl
| MissingAny | Includes the assembly only if all documents can be matched to corresponding source files. |
| None | No assembly is excluded. |

Here is an example of how to specifiy the parameter:
Here is an example of how to specify the parameter:

```shell
/p:ExcludeAssembliesWithoutSources="MissingAny"
2 changes: 1 addition & 1 deletion Documentation/Roadmap.md
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

This document describes the roadmap for coverlet showing priorities.
Maintain coverlet means like any other project two things, answer/resolve soon as possible new issues that are blocking our users an second improve product with new features and enhancements in different areas.
All coverlet issues are labeled and categorized to better support this activites.
All coverlet issues are labeled and categorized to better support this activities.

As soon as an issue is open is labeled with `untriaged` if not immediately solvable(we need to do some debugging/PoC to understand where is the issue).
After triage a final correct label is applied and will be taken into account depending on priority order.
19 changes: 11 additions & 8 deletions Documentation/Troubleshooting.md
Original file line number Diff line number Diff line change
@@ -104,7 +104,7 @@ You can "load" your local build using simple switch:
coverlet.testsubject -> D:\git\coverlet\test\coverlet.testsubject\bin\Debug\net6.0\coverlet.testsubject.dll
coverlet.core -> D:\git\coverlet\src\coverlet.core\bin\Debug\netstandard2.0\coverlet.core.dll
coverlet.msbuild.tasks -> D:\git\coverlet\src\coverlet.msbuild.tasks\bin\Debug\netstandard2.0\coverlet.msbuild.tasks.dll
coverlet.collector -> D:\git\coverlet\src\coverlet.collector\bin\Debug\net6.0\coverlet.collector.dll
coverlet.collector -> D:\git\coverlet\src\coverlet.collector\bin\Debug\netstandard2.0\coverlet.collector.dll
coverlet.console -> D:\git\coverlet\src\coverlet.console\bin\Debug\net6.0\coverlet.console.dll
coverlet.core.performancetest -> D:\git\coverlet\test\coverlet.core.performancetest\bin\Debug\net6.0\coverlet.core.performancetest.dll
coverlet.core.tests -> D:\git\coverlet\test\coverlet.core.tests\bin\Debug\net6.0\coverlet.core.tests.dll
@@ -145,9 +145,9 @@ To use/debug local collectors build we need to tell to our project to restore an
Restore completed in 50,28 ms for C:\git\coverlet\src\coverlet.collector\coverlet.collector.csproj.
Restore completed in 50,28 ms for C:\git\coverlet\src\coverlet.core\coverlet.core.csproj.
coverlet.core -> C:\git\coverlet\src\coverlet.core\bin\Debug\netstandard2.0\coverlet.core.dll
coverlet.collector -> C:\git\coverlet\src\coverlet.collector\bin\Debug\netcoreapp2.0\coverlet.collector.dll
Successfully created package 'C:\git\coverlet\bin\Debug\Packages\coverlet.collector.1.0.67.nupkg'.
Successfully created package 'C:\git\coverlet\bin\Debug\Packages\coverlet.collector.1.0.67.snupkg'.
coverlet.collector -> C:\git\coverlet\src\coverlet.collector\bin\Debug\netstandard2.0\coverlet.collector.dll
Successfully created package 'C:\git\coverlet\bin\Debug\Packages\coverlet.collector.6.0.4.nupkg'.
Successfully created package 'C:\git\coverlet\bin\Debug\Packages\coverlet.collector.6.0.4.snupkg'.
```
2) Add new `NuGet.Config` file on your test project/solution
@@ -177,10 +177,13 @@ To use/debug local collectors build we need to tell to our project to restore an
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.5" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.6" />
<PackageReference Include="coverlet.collector" Version="6.0.0" /> <-- My local package version -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<PackageReference Include="xunit.v3" Version="1.1.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="3.0.2">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.4" /> <-- My local package version -->
</ItemGroup>
<ItemGroup>
21 changes: 11 additions & 10 deletions Documentation/VSTestIntegration.md
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
Since version `6.0.0`

* .NET Core >= 6.0
* .NET Framework >= 4.6.2
* .NET Framework >= 4.7.2

As explained in quick start section, to use collectors you need to run *SDK v6.0.100* (LTS) or newer and your project file must reference `coverlet.collector` and a minimum version of `Microsoft.NET.Test.Sdk`.

@@ -14,13 +14,13 @@ A sample project file looks like:
```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0;net48</TargetFrameworks>
<TargetFramework>net8.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<!-- Minimum version 17.7.0 -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<!-- Minimum version 17.13.0 -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.13.0" />
<!-- Update this reference when new version is released -->
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PackageReference Include="coverlet.collector" Version="6.0.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -43,10 +43,10 @@ or
```text
dotnet publish
...
... -> C:\project\bin\Debug\netcoreapp3.0\testdll.dll
... -> C:\project\bin\Debug\netcoreapp3.0\publish\
... -> C:\project\bin\Debug\net6.0\testdll.dll
... -> C:\project\bin\Debug\net6.0\publish\
...
dotnet vstest C:\project\bin\Debug\netcoreapp3.0\publish\testdll.dll --collect:"XPlat Code Coverage"
dotnet vstest C:\project\bin\Debug\net6.0\publish\testdll.dll --collect:"XPlat Code Coverage"
```

As you can see in case of `vstest` verb you **must** publish project before.
@@ -59,12 +59,13 @@ Attachments:
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 2,5451 Seconds
Total time: 2.5451 Seconds
```

You can change the output directory using the standard `dotnet test` switch `--results-directory`

>*NB: By design VSTest platform will create your file under a random named folder(guid string) so if you need stable path to load file to some gui report system(i.e. coveralls, codecov, reportgenerator etc..) that doesn't support glob patterns or hierarchical search, you'll need to manually move resulting file to a predictable folder*
> [!NOTE]
>*By design VSTest platform will create your file under a random named folder(guid string) so if you need stable path to load file to some gui report system(i.e. coveralls, codecov, reportgenerator etc..) that doesn't support glob patterns or hierarchical search, you'll need to manually move resulting file to a predictable folder*
## Coverlet options supported by VSTest integration

7 changes: 0 additions & 7 deletions coverlet.sln
Original file line number Diff line number Diff line change
@@ -56,8 +56,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.integration.templa
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.core.tests.samples.netstandard", "test\coverlet.core.tests.samples.netstandard\coverlet.core.tests.samples.netstandard.csproj", "{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "coverlet.tests.xunit.extensions", "test\coverlet.tests.xunit.extensions\coverlet.tests.xunit.extensions.csproj", "{F8199E19-FA9A-4559-9101-CAD7028121B4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{9A8B19D4-4A24-4217-AEFE-159B68F029A1}"
ProjectSection(SolutionItems) = preProject
test\Directory.Build.props = test\Directory.Build.props
@@ -150,10 +148,6 @@ Global
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5FF404AD-7C0B-465A-A1E9-558CDC642B0C}.Release|Any CPU.Build.0 = Release|Any CPU
{F8199E19-FA9A-4559-9101-CAD7028121B4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
@@ -220,7 +214,6 @@ Global
{99B4059C-B25C-4B82-8117-A0E9DC9B0949} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{F6FE7678-C662-43D3-AC6A-64F6AC5A5935} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
{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}
{E69D68C9-78ED-4076-A14B-D07295A4B2A5} = {2FEBDE1B-83E3-445B-B9F8-5644B0E0E134}
2 changes: 1 addition & 1 deletion eng/azure-pipelines-nightly.yml
Original file line number Diff line number Diff line change
@@ -10,7 +10,7 @@ steps:
- task: UseDotNet@2
inputs:
useGlobalJson: true
displayName: Install .NET Core SDK 8.0.112
displayName: Install .NET Core SDK 8.0.113

- task: NuGetAuthenticate@1
displayName: Authenticate with NuGet feeds
20 changes: 10 additions & 10 deletions eng/azure-pipelines.yml
Original file line number Diff line number Diff line change
@@ -16,36 +16,36 @@ jobs:
strategy:
matrix:
Debug:
buildConfiguration: "Debug"
buildConfiguration: "debug"
Release:
buildConfiguration: "Release"
buildConfiguration: "release"
pool:
vmImage: 'windows-latest'
steps:
- template: build.yml
- task: CopyFiles@2
displayName: Collect packages
inputs:
SourceFolder: artifacts\package\$(BuildConfiguration)
SourceFolder: artifacts\package\$(buildConfiguration)
Contents: |
*.nupkg
*.snupkg
TargetFolder: $(Build.ArtifactStagingDirectory)\Packages
condition: eq(variables['BuildConfiguration'], 'Release')
condition: eq(variables['buildConfiguration'], 'release')
- task: PublishBuildArtifacts@1
displayName: Publish packages as build artifacts
inputs:
PathtoPublish: $(Build.ArtifactStagingDirectory)\Packages
ArtifactName: Packages
publishLocation: Container
condition: eq(variables['BuildConfiguration'], 'Release')
condition: eq(variables['buildConfiguration'], 'release')
- task: PublishBuildArtifacts@1
displayName: Publish tests artifacts
inputs:
PathtoPublish: $(Build.SourcesDirectory)\artifacts\publish
ArtifactName: PublishedTests
publishLocation: Container
condition: eq(variables['BuildConfiguration'], 'Debug')
condition: eq(variables['buildConfiguration'], 'debug')
- template: CheckNugetStatus.yml
parameters:
sourcePath: '$(Build.SourcesDirectory)/src'
@@ -60,9 +60,9 @@ jobs:
strategy:
matrix:
Debug:
buildConfiguration: "Debug"
buildConfiguration: "debug"
Release:
buildConfiguration: "Release"
buildConfiguration: "release"
pool:
vmImage: 'macOS-latest'
steps:
@@ -76,9 +76,9 @@ jobs:
strategy:
matrix:
Debug:
buildConfiguration: "Debug"
buildConfiguration: "debug"
Release:
buildConfiguration: "Release"
buildConfiguration: "release"
pool:
vmImage: 'ubuntu-latest'
steps:
33 changes: 33 additions & 0 deletions eng/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash

# build.sh - Helper script to build, package, and test the Coverlet project.
#
# This script performs the following tasks:
# 1. Builds the project in debug configuration and generates a binary log.
# 2. Packages the project in both debug and release configurations.
# 3. Shuts down any running .NET build servers.
# 4. Runs unit tests for various Coverlet components with code coverage enabled,
# generating binary logs and diagnostic outputs.
# 5. Outputs test results in xUnit TRX format and stores them in the specified directories.
#
# Usage:
# ./build.sh
#
# Note: Ensure that the .NET SDK is installed and available in the system PATH.

# Build the project
dotnet build -c debug -bl:build.binlog
dotnet pack -c debug
dotnet pack -c release
dotnet build-server shutdown

# Run tests with code coverage
dotnet test test/coverlet.core.tests/coverlet.core.tests.csproj -c debug --no-build -bl:test.core.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" -- --results-directory "artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.core.tests.trx" --diagnostic --diagnostic-output-directory "artifacts/log/debug" --diagnostic-output-fileprefix "coverlet.core.tests"
dotnet build-server shutdown
dotnet test test/coverlet.core.coverage.tests/coverlet.core.coverage.tests.csproj -c debug --no-build -bl:test.core.coverage.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" -- --results-directory "artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.core.coverage.tests.trx" --diagnostic --diagnostic-output-directory "artifacts/log/debug" --diagnostic-output-fileprefix "coverlet.core.coverage.tests"
dotnet build-server shutdown
dotnet test test/coverlet.msbuild.tasks.tests/coverlet.msbuild.tasks.tests.csproj -c debug --no-build -bl:test.msbuild.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" -- --results-directory:"artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.msbuild.tasks.tests.trx" --diagnostic --diagnostic-output-directory "artifacts/log/debug" --diagnostic-output-fileprefix "coverlet.msbuild.tasks.tests"
dotnet build-server shutdown
dotnet test test/coverlet.collector.tests/coverlet.collector.tests.csproj -c debug --no-build -bl:test.collector.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" --diag:"artifacts/log/debug/coverlet.collector.test.diag.log;tracelevel=verbose"
dotnet build-server shutdown
dotnet test test/coverlet.integration.tests/coverlet.integration.tests.csproj -c debug --no-build -bl:test.integration.binlog -- --results-directory "artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.integration.tests.trx" --diagnostic --diagnostic-output-directory "artifacts/log/debug" --diagnostic-output-fileprefix "coverlet.integration.tests"
14 changes: 7 additions & 7 deletions eng/build.yml
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@ steps:
- task: UseDotNet@2
inputs:
useGlobalJson: true
displayName: Install .NET Core SDK 8.0.112
displayName: Install .NET Core SDK 8.0.113

# create artifact/package folder
- pwsh: |
@@ -25,11 +25,11 @@ steps:
displayName: Pack

- script: |
dotnet test test/coverlet.collector.tests/coverlet.collector.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.collector.binlog --results-directory:"$(Build.SourcesDirectory)\artifacts\Reports" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.xunit.extensions]*%2c[coverlet.tests.projectsample]*%2c[testgen_]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" --diag:"$(Build.SourcesDirectory)\artifacts\log\$(buildConfiguration)\coverlet.collector.test.diag.log;tracelevel=verbose"
dotnet test test/coverlet.core.tests/coverlet.core.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.core.binlog --results-directory:"$(Build.SourcesDirectory)\artifacts\Reports" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.xunit.extensions]*%2c[coverlet.tests.projectsample]*%2c[testgen_]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" --diag:"$(Build.SourcesDirectory)\artifacts\log\$(buildConfiguration)\coverlet.core.test.diag.log;tracelevel=verbose"
dotnet test test/coverlet.core.coverage.tests/coverlet.core.coverage.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.core.coverage.binlog --results-directory:"$(Build.SourcesDirectory)\artifacts\Reports" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.xunit.extensions]*%2c[coverlet.tests.projectsample]*%2c[testgen_]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" --diag:"$(Build.SourcesDirectory)\artifacts\log\$(buildConfiguration)\coverlet.core.coverage.test.diag.log;tracelevel=verbose"
dotnet test test/coverlet.core.tests/coverlet.core.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.core.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" -- --results-directory "$(Build.SourcesDirectory)/artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.core.tests.trx" --diagnostic --diagnostic-output-directory "$(Build.SourcesDirectory)/artifacts/log/$(buildConfiguration)" --diagnostic-output-fileprefix "coverlet.core.tests"
dotnet test test/coverlet.core.coverage.tests/coverlet.core.coverage.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.core.coverage.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" -- --results-directory "$(Build.SourcesDirectory)/artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.core.coverage.tests.trx" --diagnostic --diagnostic-output-directory "$(Build.SourcesDirectory)/artifacts/log/$(buildConfiguration)" --diagnostic-output-fileprefix "coverlet.core.coverage.tests"
dotnet test test/coverlet.msbuild.tasks.tests/coverlet.msbuild.tasks.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.msbuild.binlog --results-directory:"$(Build.SourcesDirectory)\artifacts\Reports" /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.xunit.extensions]*%2c[coverlet.tests.projectsample]*%2c[testgen_]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" --diag:"$(Build.SourcesDirectory)\artifacts\log\$(buildConfiguration)\coverlet.msbuild.test.diag.log;tracelevel=verbose"
dotnet test test/coverlet.integration.tests/coverlet.integration.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.integration.binlog --results-directory:"$(Build.SourcesDirectory)\artifacts\Reports" --diag:"$(Build.SourcesDirectory)\artifacts\log\$(buildConfiguration)\coverlet.integration.test.diag.log;tracelevel=verbose"
dotnet test test/coverlet.collector.tests/coverlet.collector.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.collector.binlog /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Exclude="[coverlet.core.tests.samples.netstandard]*%2c[coverlet.tests.projectsample]*" /p:ExcludeByAttribute="GeneratedCodeAttribute" --diag:"$(Build.SourcesDirectory)/artifacts/log/$(buildConfiguration)/coverlet.collector.test.diag.log;tracelevel=verbose"
dotnet test test/coverlet.integration.tests/coverlet.integration.tests.csproj -c $(BuildConfiguration) --no-build -bl:test.integration.binlog -- --results-directory "$(Build.SourcesDirectory)/artifacts/Reports" --report-xunit-trx --report-xunit-trx-filename "coverlet.integration.tests.trx" --diagnostic --diagnostic-output-directory "$(Build.SourcesDirectory)/artifacts/log/$(buildConfiguration)" --diagnostic-output-fileprefix "coverlet.integration.tests"
displayName: Run unit tests with coverage
env:
MSBUILDDISABLENODEREUSE: 1
@@ -49,6 +49,6 @@ steps:
- template: publish-coverage-results.yml
parameters:
reports: $(Build.SourcesDirectory)\**\*.opencover.xml
condition: and(succeededORFailed(), eq(variables['BuildConfiguration'], 'Debug'), eq(variables['agent.os'], 'Windows_NT'))
assemblyfilters: '-xunit;-coverlet.testsubject;-Coverlet.Tests.ProjectSample.*;-coverlet.core.tests.samples.netstandard;-coverlet.tests.xunit.extensions;-coverletsamplelib.integration.template;-coverlet.tests.utils'
condition: and(succeededORFailed(), eq(variables['buildConfiguration'], 'debug'), eq(variables['agent.os'], 'Windows_NT'))
assemblyfilters: '-xunit;-coverlet.testsubject;-Coverlet.Tests.ProjectSample.*;-coverlet.core.tests.samples.netstandard;-coverletsamplelib.integration.template;-coverlet.tests.utils'

2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "8.0.112"
"version": "8.0.113"
}
}
42 changes: 32 additions & 10 deletions src/coverlet.collector/DataCollection/AttachmentManager.cs
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ internal class AttachmentManager : IDisposable
private readonly IDirectoryHelper _directoryHelper;
private readonly ICountDownEvent _countDownEvent;
private readonly string _reportDirectory;
private bool _disposed;

public AttachmentManager(DataCollectionSink dataSink, DataCollectionContext dataCollectionContext, TestPlatformLogger logger, TestPlatformEqtTrace eqtTrace, ICountDownEvent countDownEvent)
: this(dataSink,
@@ -73,22 +74,43 @@ public void SendCoverageReport(string coverageReport, string coverageReportFileN
/// </summary>
public void Dispose()
{
// Unregister events
try
Dispose(true);
GC.SuppressFinalize(this);
}

protected virtual void Dispose(bool disposing)
{
if (!_disposed)
{
_countDownEvent.Wait();
if (_dataSink != null)
if (disposing)
{
_dataSink.SendFileCompleted -= OnSendFileCompleted;
// Unregister events
try
{
_countDownEvent.Wait();
if (_dataSink != null)
{
_dataSink.SendFileCompleted -= OnSendFileCompleted;
}
CleanupReportDirectory();
}
catch (Exception ex)
{
_logger.LogWarning(ex.ToString());
}
}
CleanupReportDirectory();
}
catch (Exception ex)
{
_logger.LogWarning(ex.ToString());

// Free any unmanaged resources here if there are any

_disposed = true;
}
}

~AttachmentManager()
{
Dispose(false);
}

/// <summary>
/// Saves coverage report to file system
/// </summary>
Original file line number Diff line number Diff line change
@@ -85,7 +85,7 @@ public void TestSessionStart(TestSessionStartArgs testSessionStartArgs)
{
}

private Type GetInstrumentationClass(Assembly assembly)
internal Type GetInstrumentationClass(Assembly assembly)
{
try
{
1 change: 0 additions & 1 deletion src/coverlet.collector/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -5,7 +5,6 @@
using System.Runtime.CompilerServices;

[assembly: AssemblyKeyFile("coverlet.collector.snk")]
[assembly: InternalsVisibleTo("coverlet.core.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100757cf9291d78a82e5bb58a827a3c46c2f959318327ad30d1b52e918321ffbd847fb21565b8576d2a3a24562a93e86c77a298b564a0f1b98f63d7a1441a3a8bcc206da3ed09d5dacc76e122a109a9d3ac608e21a054d667a2bae98510a1f0f653c0e6f58f42b4b3934f6012f5ec4a09b3dfd3e14d437ede1424bdb722aead64ad")]
[assembly: InternalsVisibleTo("coverlet.collector.tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100ed0ed6af9693182615b8dcadc83c918b8d36312f86cefc69539d67d4189cd1b89420e7c3871802ffef7f5ca7816c68ad856c77bf7c230cc07824d96aa5d1237eebd30e246b9a14e22695fb26b40c800f74ea96619092cbd3a5d430d6c003fc7a82e8ccd1e315b935105d9232fe9e99e8d7ff54bba6f191959338d4a3169df9b3")]
// Needed to mock internal type https://github.com/Moq/moq4/wiki/Quickstart#advanced-features
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")]
7 changes: 2 additions & 5 deletions src/coverlet.collector/coverlet.collector.csproj
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyTitle>coverlet.collector</AssemblyTitle>
<DevelopmentDependency>true</DevelopmentDependency>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
@@ -40,9 +40,6 @@
<ItemGroup>
<PackageReference Include="Microsoft.TestPlatform.ObjectModel" />
<PackageReference Include="NuGet.Frameworks" />
<PackageReference Include="System.Buffers" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
</ItemGroup>

<ItemGroup>
@@ -62,7 +59,7 @@
</None>
</ItemGroup>

<Target Name="PackBuildOutputs">
<Target Name="PackBuildOutputs" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
<TfmSpecificPackageFile Include="build\coverlet.collector.targets" PackagePath="build\$(TargetFramework)" />
<TfmSpecificPackageFile Include="$(TargetPath)" PackagePath="build\$(TargetFramework)" />
23 changes: 11 additions & 12 deletions src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
@@ -251,7 +251,7 @@ string sourceMappingFile
IReporter reporter = new ReporterFactory(format).CreateReporter();
if (reporter == null)
{
throw new Exception($"Specified output format '{format}' is not supported");
throw new InvalidOperationException($"Specified output format '{format}' is not supported");
}

if (reporter.OutputType == ReporterOutputType.Console)
@@ -297,7 +297,7 @@ string sourceMappingFile
IEnumerable<string> thresholdValues = threshold.Split(',', StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim());
if (thresholdValues.Count() != thresholdTypeFlagQueue.Count)
{
throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match");
throw new InvalidOperationException($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match");
}

foreach (string thresholdValue in thresholdValues)
@@ -308,7 +308,7 @@ string sourceMappingFile
}
else
{
throw new Exception($"Invalid threshold value must be numeric");
throw new InvalidOperationException($"Invalid threshold value must be numeric");
}
}
}
@@ -323,11 +323,10 @@ string sourceMappingFile
}

var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
var summary = new CoverageSummary();

CoverageDetails linePercentCalculation = summary.CalculateLineCoverage(result.Modules);
CoverageDetails branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules);
CoverageDetails methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules);
CoverageDetails linePercentCalculation = CoverageSummary.CalculateLineCoverage(result.Modules);
CoverageDetails branchPercentCalculation = CoverageSummary.CalculateBranchCoverage(result.Modules);
CoverageDetails methodPercentCalculation = CoverageSummary.CalculateMethodCoverage(result.Modules);

double totalLinePercent = linePercentCalculation.Percent;
double totalBranchPercent = branchPercentCalculation.Percent;
@@ -339,9 +338,9 @@ string sourceMappingFile

foreach (KeyValuePair<string, Documents> _module in result.Modules)
{
double linePercent = summary.CalculateLineCoverage(_module.Value).Percent;
double branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent;
double methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent;
double linePercent = CoverageSummary.CalculateLineCoverage(_module.Value).Percent;
double branchPercent = CoverageSummary.CalculateBranchCoverage(_module.Value).Percent;
double methodPercent = CoverageSummary.CalculateMethodCoverage(_module.Value).Percent;

coverageTable.AddRow(Path.GetFileNameWithoutExtension(_module.Key), $"{InvariantFormat(linePercent)}%", $"{InvariantFormat(branchPercent)}%", $"{InvariantFormat(methodPercent)}%");
}
@@ -361,7 +360,7 @@ string sourceMappingFile
exitCode += (int)CommandExitCodes.TestFailed;
}

ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStat);
ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(thresholdTypeFlagValues, thresholdStat);
if (thresholdTypeFlags != ThresholdTypeFlags.None)
{
exitCode += (int)CommandExitCodes.CoverageBelowThreshold;
@@ -380,7 +379,7 @@ string sourceMappingFile
{
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}");
}
throw new Exception(exceptionMessageBuilder.ToString());
throw new InvalidOperationException(exceptionMessageBuilder.ToString());
}

return Task.FromResult(exitCode);
4 changes: 4 additions & 0 deletions src/coverlet.console/Properties/AssemblyInfo.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
// Copyright (c) Toni Solarin-Sodara
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System.Runtime.CompilerServices;

[assembly: System.Reflection.AssemblyKeyFileAttribute("coverlet.console.snk")]
[assembly: InternalsVisibleTo("coverlet.integration.tests, PublicKey=002400000480000094000000060200000024000052534131000400000100010001d24efbe9cbc2dc49b7a3d2ae34ca37cfb69b4f450acd768a22ce5cd021c8a38ae7dc68b2809a1ac606ad531b578f192a5690b2986990cbda4dd84ec65a3a4c1c36f6d7bb18f08592b93091535eaee2f0c8e48763ed7f190db2008e1f9e0facd5c0df5aaab74febd3430e09a428a72e5e6b88357f92d78e47512d46ebdc3cbb")]

2 changes: 1 addition & 1 deletion src/coverlet.console/coverlet.console.csproj
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<ToolCommandName>coverlet</ToolCommandName>
<PackAsTool>true</PackAsTool>
<AssemblyTitle>coverlet.console</AssemblyTitle>
17 changes: 7 additions & 10 deletions src/coverlet.core/Coverage.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) Toni Solarin-Sodara
// Copyright (c) Toni Solarin-Sodara
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
@@ -395,17 +395,14 @@ private void CalculateCoverage()

foreach (HitCandidate hitCandidateToCompare in result.HitCandidates.Where(x => x.docIndex.Equals(hitCandidate.docIndex)))
{
if (hitCandidate != hitCandidateToCompare && !hitCandidateToCompare.isBranch)
{
if (hitCandidateToCompare.start > hitCandidate.start &&
if (hitCandidate != hitCandidateToCompare && !hitCandidateToCompare.isBranch && hitCandidateToCompare.start > hitCandidate.start &&
hitCandidateToCompare.end < hitCandidate.end)
{
for (int i = hitCandidateToCompare.start;
i <= (hitCandidateToCompare.end == 0 ? hitCandidateToCompare.start : hitCandidateToCompare.end);
i++)
{
for (int i = hitCandidateToCompare.start;
i <= (hitCandidateToCompare.end == 0 ? hitCandidateToCompare.start : hitCandidateToCompare.end);
i++)
{
(hitCandidate.AccountedByNestedInstrumentation ??= []).Add(i);
}
(hitCandidate.AccountedByNestedInstrumentation ??= []).Add(i);
}
}
}
29 changes: 14 additions & 15 deletions src/coverlet.core/CoverageResult.cs
Original file line number Diff line number Diff line change
@@ -24,8 +24,8 @@ internal class Method
{
internal Method()
{
Lines = new Lines();
Branches = new Branches();
Lines = [];
Branches = [];
}

public Lines Lines;
@@ -110,7 +110,7 @@ public void Merge(Modules modules)
}
}

public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, Dictionary<ThresholdTypeFlags, double> thresholdTypeFlagValues, ThresholdStatistic thresholdStat)
public ThresholdTypeFlags GetThresholdTypesBelowThreshold(Dictionary<ThresholdTypeFlags, double> thresholdTypeFlagValues, ThresholdStatistic thresholdStat)
{
ThresholdTypeFlags thresholdTypeFlags = ThresholdTypeFlags.None;
switch (thresholdStat)
@@ -119,31 +119,30 @@ public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summar
{
if (!Modules.Any())
thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, 0, 0, 0);

foreach (KeyValuePair<string, Documents> module in Modules)
foreach ((double line, double branch, double method) in from KeyValuePair<string, Documents> module in Modules
let line = CoverageSummary.CalculateLineCoverage(module.Value).Percent
let branch = CoverageSummary.CalculateBranchCoverage(module.Value).Percent
let method = CoverageSummary.CalculateMethodCoverage(module.Value).Percent
select (line, branch, method))
{
double line = summary.CalculateLineCoverage(module.Value).Percent;
double branch = summary.CalculateBranchCoverage(module.Value).Percent;
double method = summary.CalculateMethodCoverage(module.Value).Percent;

thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, line, branch, method);
}
}
break;
case ThresholdStatistic.Average:
{
double line = summary.CalculateLineCoverage(Modules).AverageModulePercent;
double branch = summary.CalculateBranchCoverage(Modules).AverageModulePercent;
double method = summary.CalculateMethodCoverage(Modules).AverageModulePercent;
double line = CoverageSummary.CalculateLineCoverage(Modules).AverageModulePercent;
double branch = CoverageSummary.CalculateBranchCoverage(Modules).AverageModulePercent;
double method = CoverageSummary.CalculateMethodCoverage(Modules).AverageModulePercent;

thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, line, branch, method);
}
break;
case ThresholdStatistic.Total:
{
double line = summary.CalculateLineCoverage(Modules).Percent;
double branch = summary.CalculateBranchCoverage(Modules).Percent;
double method = summary.CalculateMethodCoverage(Modules).Percent;
double line = CoverageSummary.CalculateLineCoverage(Modules).Percent;
double branch = CoverageSummary.CalculateBranchCoverage(Modules).Percent;
double method = CoverageSummary.CalculateMethodCoverage(Modules).Percent;

thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, line, branch, method);
}
72 changes: 39 additions & 33 deletions src/coverlet.core/CoverageSummary.cs
Original file line number Diff line number Diff line change
@@ -9,15 +9,17 @@ namespace Coverlet.Core
{
internal class CoverageSummary
{
public CoverageDetails CalculateLineCoverage(Lines lines)
public static CoverageDetails CalculateLineCoverage(Lines lines)
{
var details = new CoverageDetails();
details.Covered = lines.Where(l => l.Value > 0).Count();
details.Total = lines.Count;
var details = new CoverageDetails
{
Covered = lines.Count(l => l.Value > 0),
Total = lines.Count
};
return details;
}

public CoverageDetails CalculateLineCoverage(Methods methods)
public static CoverageDetails CalculateLineCoverage(Methods methods)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Method> method in methods)
@@ -29,7 +31,7 @@ public CoverageDetails CalculateLineCoverage(Methods methods)
return details;
}

public CoverageDetails CalculateLineCoverage(Classes classes)
public static CoverageDetails CalculateLineCoverage(Classes classes)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Methods> @class in classes)
@@ -41,7 +43,7 @@ public CoverageDetails CalculateLineCoverage(Classes classes)
return details;
}

public CoverageDetails CalculateLineCoverage(Documents documents)
public static CoverageDetails CalculateLineCoverage(Documents documents)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Classes> document in documents)
@@ -53,7 +55,7 @@ public CoverageDetails CalculateLineCoverage(Documents documents)
return details;
}

public CoverageDetails CalculateLineCoverage(Modules modules)
public static CoverageDetails CalculateLineCoverage(Modules modules)
{
var details = new CoverageDetails { Modules = modules };
double accumPercent = 0.0D;
@@ -72,15 +74,17 @@ public CoverageDetails CalculateLineCoverage(Modules modules)
return details;
}

public CoverageDetails CalculateBranchCoverage(IList<BranchInfo> branches)
public static CoverageDetails CalculateBranchCoverage(IList<BranchInfo> branches)
{
var details = new CoverageDetails();
details.Covered = branches.Count(bi => bi.Hits > 0);
details.Total = branches.Count;
var details = new CoverageDetails
{
Covered = branches.Count(bi => bi.Hits > 0),
Total = branches.Count
};
return details;
}

public int CalculateNpathComplexity(IList<BranchInfo> branches)
public static int CalculateNpathComplexity(IList<BranchInfo> branches)
{
// Adapted from OpenCover see https://github.com/OpenCover/opencover/blob/master/main/OpenCover.Framework/Persistance/BasePersistance.cs#L419
if (!branches.Any())
@@ -114,47 +118,47 @@ public int CalculateNpathComplexity(IList<BranchInfo> branches)
return npath;
}

public int CalculateCyclomaticComplexity(IList<BranchInfo> branches)
public static int CalculateCyclomaticComplexity(IList<BranchInfo> branches)
{
return Math.Max(1, branches.Count);
}

public int CalculateCyclomaticComplexity(Methods methods)
public static int CalculateCyclomaticComplexity(Methods methods)
{
return methods.Values.Select(m => CalculateCyclomaticComplexity(m.Branches)).Sum();
}

public int CalculateMaxCyclomaticComplexity(Methods methods)
public static int CalculateMaxCyclomaticComplexity(Methods methods)
{
return methods.Values.Select(m => CalculateCyclomaticComplexity(m.Branches)).DefaultIfEmpty(1).Max();
}

public int CalculateMinCyclomaticComplexity(Methods methods)
public static int CalculateMinCyclomaticComplexity(Methods methods)
{
return methods.Values.Select(m => CalculateCyclomaticComplexity(m.Branches)).DefaultIfEmpty(1).Min();
}

public int CalculateCyclomaticComplexity(Modules modules)
public static int CalculateCyclomaticComplexity(Modules modules)
{
return modules.Values.Select(CalculateCyclomaticComplexity).Sum();
}

public int CalculateMaxCyclomaticComplexity(Modules modules)
public static int CalculateMaxCyclomaticComplexity(Modules modules)
{
return modules.Values.Select(CalculateCyclomaticComplexity).DefaultIfEmpty(1).Max();
}

public int CalculateMinCyclomaticComplexity(Modules modules)
public static int CalculateMinCyclomaticComplexity(Modules modules)
{
return modules.Values.Select(CalculateCyclomaticComplexity).DefaultIfEmpty(1).Min();
}

public int CalculateCyclomaticComplexity(Documents documents)
public static int CalculateCyclomaticComplexity(Documents documents)
{
return documents.Values.SelectMany(c => c.Values.Select(CalculateCyclomaticComplexity)).Sum();
}

public CoverageDetails CalculateBranchCoverage(Methods methods)
public static CoverageDetails CalculateBranchCoverage(Methods methods)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Method> method in methods)
@@ -166,7 +170,7 @@ public CoverageDetails CalculateBranchCoverage(Methods methods)
return details;
}

public CoverageDetails CalculateBranchCoverage(Classes classes)
public static CoverageDetails CalculateBranchCoverage(Classes classes)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Methods> @class in classes)
@@ -178,7 +182,7 @@ public CoverageDetails CalculateBranchCoverage(Classes classes)
return details;
}

public CoverageDetails CalculateBranchCoverage(Documents documents)
public static CoverageDetails CalculateBranchCoverage(Documents documents)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Classes> document in documents)
@@ -190,7 +194,7 @@ public CoverageDetails CalculateBranchCoverage(Documents documents)
return details;
}

public CoverageDetails CalculateBranchCoverage(Modules modules)
public static CoverageDetails CalculateBranchCoverage(Modules modules)
{
var details = new CoverageDetails { Modules = modules };
double accumPercent = 0.0D;
@@ -209,15 +213,17 @@ public CoverageDetails CalculateBranchCoverage(Modules modules)
return details;
}

public CoverageDetails CalculateMethodCoverage(Lines lines)
public static CoverageDetails CalculateMethodCoverage(Lines lines)
{
var details = new CoverageDetails();
details.Covered = lines.Any(l => l.Value > 0) ? 1 : 0;
details.Total = 1;
var details = new CoverageDetails
{
Covered = lines.Any(l => l.Value > 0) ? 1 : 0,
Total = 1
};
return details;
}

public CoverageDetails CalculateMethodCoverage(Methods methods)
public static CoverageDetails CalculateMethodCoverage(Methods methods)
{
var details = new CoverageDetails();
IEnumerable<KeyValuePair<string, Method>> methodsWithLines = methods.Where(m => m.Value.Lines.Count > 0);
@@ -230,7 +236,7 @@ public CoverageDetails CalculateMethodCoverage(Methods methods)
return details;
}

public CoverageDetails CalculateMethodCoverage(Classes classes)
public static CoverageDetails CalculateMethodCoverage(Classes classes)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Methods> @class in classes)
@@ -242,7 +248,7 @@ public CoverageDetails CalculateMethodCoverage(Classes classes)
return details;
}

public CoverageDetails CalculateMethodCoverage(Documents documents)
public static CoverageDetails CalculateMethodCoverage(Documents documents)
{
var details = new CoverageDetails();
foreach (KeyValuePair<string, Classes> document in documents)
@@ -254,7 +260,7 @@ public CoverageDetails CalculateMethodCoverage(Documents documents)
return details;
}

public CoverageDetails CalculateMethodCoverage(Modules modules)
public static CoverageDetails CalculateMethodCoverage(Modules modules)
{
var details = new CoverageDetails { Modules = modules };
double accumPercent = 0.0D;
2 changes: 0 additions & 2 deletions src/coverlet.core/Helpers/InstrumentationHelper.cs
Original file line number Diff line number Diff line change
@@ -468,10 +468,8 @@ private static bool IsTypeFilterMatch(string module, string type, string[] filte

foreach (string filter in filters)
{
#pragma warning disable IDE0057 // Use range operator
string typePattern = filter.Substring(filter.IndexOf(']') + 1);
string modulePattern = filter.Substring(1, filter.IndexOf(']') - 1);
#pragma warning restore IDE0057 // Use range operator

typePattern = WildcardToRegex(typePattern);
modulePattern = WildcardToRegex(modulePattern);
8 changes: 7 additions & 1 deletion src/coverlet.core/Helpers/RetryHelper.cs
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using Coverlet.Core.Abstractions;

@@ -54,7 +55,12 @@ public T Do<T>(
}
return action();
}
catch (Exception ex)
catch (DirectoryNotFoundException)
{
// do nothing
return default;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't have that much time to look into everything but in which scenario we don't want to throw the exception? E.g. if someone/something deletes the temp directory with the orginal modules before we could restore them? Is this something we can just ignore because the user will be notified in another place?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will suppress a retry for a not existing folder or not accessible folder. I had this situation which finally makes any retry useless.

By the way, this code was copied a long time ago (2017) from stackoverflow. Newer comments states that this implementation is buggy and Polly was recommended as an alternative.

}
catch (IOException ex)
{
exceptions.Add(ex);
}
12 changes: 5 additions & 7 deletions src/coverlet.core/Helpers/SourceRootTranslator.cs
Original file line number Diff line number Diff line change
@@ -28,7 +28,7 @@ public SourceRootTranslator(ILogger logger, IFileSystem fileSystem)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_fileSystem = fileSystem ?? throw new ArgumentNullException(nameof(fileSystem));
_sourceRootMapping = new Dictionary<string, List<SourceRootMapping>>();
_sourceRootMapping = [];
}

public SourceRootTranslator(string sourceMappingFile, ILogger logger, IFileSystem fileSystem)
@@ -74,7 +74,7 @@ private static Dictionary<string, List<string>> LoadSourceToDeterministicPathMap
{
if (!sourceToDeterministicPathMapping.ContainsKey(originalPath.OriginalPath))
{
sourceToDeterministicPathMapping.Add(originalPath.OriginalPath, new List<string>());
sourceToDeterministicPathMapping.Add(originalPath.OriginalPath, []);
}
sourceToDeterministicPathMapping[originalPath.OriginalPath].Add(sourceRootMappingEntry.Key);
}
@@ -101,15 +101,13 @@ private Dictionary<string, List<SourceRootMapping>> LoadSourceRootMapping(string
_logger.LogWarning($"Malformed mapping '{mappingRecord}'");
continue;
}
#pragma warning disable IDE0057 // Use range operator
string projectPath = mappingRecord.Substring(0, projectFileSeparatorIndex);
string originalPath = mappingRecord.Substring(projectFileSeparatorIndex + 1, pathMappingSeparatorIndex - projectFileSeparatorIndex - 1);
string mappedPath = mappingRecord.Substring(pathMappingSeparatorIndex + 1);
#pragma warning restore IDE0057 // Use range operator

if (!mapping.ContainsKey(mappedPath))
{
mapping.Add(mappedPath, new List<SourceRootMapping>());
mapping.Add(mappedPath, []);
}

foreach (string path in originalPath.Split(';'))
@@ -128,7 +126,7 @@ public bool AddMappingInCache(string originalFileName, string targetFileName)
return false;
}

(_resolutionCacheFiles ??= new Dictionary<string, string>()).Add(originalFileName, targetFileName);
(_resolutionCacheFiles ??= []).Add(originalFileName, targetFileName);
return true;
}

@@ -153,7 +151,7 @@ public string ResolveFilePath(string originalFileName)
string pathToCheck;
if (_fileSystem.Exists(pathToCheck = Path.GetFullPath(originalFileName.Replace(mapping.Key, srm.OriginalPath))))
{
(_resolutionCacheFiles ??= new Dictionary<string, string>()).Add(originalFileName, pathToCheck);
(_resolutionCacheFiles ??= []).Add(originalFileName, pathToCheck);
_logger.LogVerbose($"Mapping resolved: '{FileSystem.EscapeFileName(originalFileName)}' -> '{FileSystem.EscapeFileName(pathToCheck)}'");
return pathToCheck;
}
12 changes: 5 additions & 7 deletions src/coverlet.core/Instrumentation/CecilAssemblyResolver.cs
Original file line number Diff line number Diff line change
@@ -74,13 +74,13 @@ public NetstandardAwareAssemblyResolver(string modulePath, ILogger logger)

// this is lazy because we cannot create NetCoreSharedFrameworkResolver if not on .NET Core runtime,
// runtime folders are different
_compositeResolver = new Lazy<CompositeCompilationAssemblyResolver>(() => new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
{
_compositeResolver = new Lazy<CompositeCompilationAssemblyResolver>(() => new CompositeCompilationAssemblyResolver(
[
new NetCoreSharedFrameworkResolver(modulePath, _logger),
new AppBaseCompilationAssemblyResolver(),
new PackageCompilationAssemblyResolver(),
new ReferenceAssemblyPathResolver(),
}), true);
]), true);
}

// Check name and public key but not version that could be different
@@ -220,7 +220,7 @@ internal AssemblyDefinition TryWithCustomResolverOnDotNetCore(AssemblyNameRefere

internal class NetCoreSharedFrameworkResolver : ICompilationAssemblyResolver
{
private readonly List<string> _aspNetSharedFrameworkDirs = new();
private readonly List<string> _aspNetSharedFrameworkDirs = [];
private readonly ILogger _logger;

public NetCoreSharedFrameworkResolver(string modulePath, ILogger logger)
@@ -244,10 +244,8 @@ public NetCoreSharedFrameworkResolver(string modulePath, ILogger logger)
var semVersion = NuGetVersion.Parse(frameworkVersion);
var directory = new DirectoryInfo(Path.Combine(runtimeRootPath, frameworkName));
string majorVersion = $"{semVersion.Major}.{semVersion.Minor}.";
#pragma warning disable IDE0057 // Use range operator
uint latestVersion = directory.GetDirectories().Where(x => x.Name.StartsWith(majorVersion))
.Select(x => Convert.ToUInt32(x.Name.Substring(majorVersion.Length))).Max();
#pragma warning restore IDE0057 // Use range operator
_aspNetSharedFrameworkDirs.Add(Directory.GetDirectories(directory.FullName, majorVersion + $"{latestVersion}*", SearchOption.TopDirectoryOnly)[0]);
}

@@ -310,7 +308,7 @@ public RuntimeConfigurationReader(string runtimeConfigFile)

if (runtimeOptionsElement?["framework"] != null)
{
return new[] { (runtimeOptionsElement["framework"]["name"]?.Value<string>(), runtimeOptionsElement["framework"]["version"]?.Value<string>()) };
return [(runtimeOptionsElement["framework"]["name"]?.Value<string>(), runtimeOptionsElement["framework"]["version"]?.Value<string>())];
}

if (runtimeOptionsElement?["frameworks"] != null)
63 changes: 31 additions & 32 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
@@ -78,7 +78,7 @@ public Instrumenter(
_excludeAssembliesWithoutSources = DetermineHeuristics(parameters.ExcludeAssembliesWithoutSources);
}

private AssemblySearchType DetermineHeuristics(string parametersExcludeAssembliesWithoutSources)
private static AssemblySearchType DetermineHeuristics(string parametersExcludeAssembliesWithoutSources)
{
if (Enum.TryParse(parametersExcludeAssembliesWithoutSources, true, out AssemblySearchType option))
{
@@ -90,13 +90,12 @@ private AssemblySearchType DetermineHeuristics(string parametersExcludeAssemblie
private static string[] PrepareAttributes(IEnumerable<string> providedAttrs, params string[] defaultAttrs)
{
return
(providedAttrs ?? Array.Empty<string>())
[.. (providedAttrs ?? [])
// In case the attribute class ends in "Attribute", but it wasn't specified.
// Both names are included (if it wasn't specified) because the attribute class might not actually end in the prefix.
.SelectMany(a => a.EndsWith("Attribute") ? new[] { a } : new[] { a, $"{a}Attribute" })
.SelectMany(a => a.EndsWith("Attribute") ? [a] : new[] { a, $"{a}Attribute" })
// The default custom attributes used to exclude from coverage.
.Union(defaultAttrs)
.ToArray();
.Union(defaultAttrs)];
}

public bool CanInstrument()
@@ -155,7 +154,7 @@ public InstrumenterResult Instrument()
}
}

_result.BranchesInCompiledGeneratedClass = _branchesInCompiledGeneratedClass == null ? Array.Empty<string>() : _branchesInCompiledGeneratedClass.ToArray();
_result.BranchesInCompiledGeneratedClass = _branchesInCompiledGeneratedClass == null ? [] : [.. _branchesInCompiledGeneratedClass];

return _result;
}
@@ -252,7 +251,7 @@ private void InstrumentModule()
nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef);
}

Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last();
Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions[_customTrackerClassConstructorIl.Body.Instructions.Count - 1];

if (!containsAppContext)
{
@@ -313,7 +312,7 @@ private void InstrumentModule()
}
var handler = new ExceptionHandler(ExceptionHandlerType.Finally)
{
TryStart = onProcessExitIl.Body.Instructions.First(),
TryStart = onProcessExitIl.Body.Instructions[0],
TryEnd = firstNullParam,
HandlerStart = firstNullParam,
HandlerEnd = ret
@@ -333,13 +332,16 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
"Coverlet.Core.Instrumentation", nameof(ModuleTrackerTemplate));

_customTrackerTypeDef = new TypeDefinition(
"Coverlet.Core.Instrumentation.Tracker", Path.GetFileNameWithoutExtension(module.Name) + "_" + _identifier, moduleTrackerTemplate.Attributes);

_customTrackerTypeDef.BaseType = module.TypeSystem.Object;
"Coverlet.Core.Instrumentation.Tracker", Path.GetFileNameWithoutExtension(module.Name) + "_" + _identifier, moduleTrackerTemplate.Attributes)
{
BaseType = module.TypeSystem.Object
};
foreach (FieldDefinition fieldDef in moduleTrackerTemplate.Fields)
{
var fieldClone = new FieldDefinition(fieldDef.Name, fieldDef.Attributes, fieldDef.FieldType);
fieldClone.FieldType = module.ImportReference(fieldDef.FieldType);
var fieldClone = new FieldDefinition(fieldDef.Name, fieldDef.Attributes, fieldDef.FieldType)
{
FieldType = module.ImportReference(fieldDef.FieldType)
};

_customTrackerTypeDef.Fields.Add(fieldClone);

@@ -443,12 +445,9 @@ private bool IsMethodOfCompilerGeneratedClassOfAsyncStateMachineToBeExcluded(Met
// If the async state machine generated by compiler is "associated" to this method we check for exclusions
// The associated type is specified on attribute constructor
// https://docs.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.asyncstatemachineattribute.-ctor?view=netcore-3.1
if (attribute.ConstructorArguments[0].Value == method.DeclaringType)
if (attribute.ConstructorArguments[0].Value == method.DeclaringType && typeMethod.CustomAttributes.Any(IsExcludeAttribute))
{
if (typeMethod.CustomAttributes.Any(IsExcludeAttribute))
{
return true;
}
return true;
}
}
}
@@ -464,9 +463,7 @@ private void InstrumentType(TypeDefinition type)
MethodDefinition actualMethod = method;
IEnumerable<CustomAttribute> customAttributes = method.CustomAttributes;
if (_instrumentationHelper.IsLocalMethod(method.Name))
#pragma warning disable IDE0057 // Use range operator
actualMethod = methods.FirstOrDefault(m => m.Name == method.Name.Split('>')[0].Substring(1)) ?? method;
#pragma warning restore IDE0057 // Use range operator

if (actualMethod.IsGetter || actualMethod.IsSetter)
{
@@ -497,8 +494,8 @@ private void InstrumentType(TypeDefinition type)
}
else
{
(_excludedLambdaMethods ??= new List<string>()).AddRange(CollectLambdaMethodsInsideLocalFunction(method));
_excludedMethodSections ??= new List<(SequencePoint firstSequencePoint, SequencePoint lastSequencePoint)>();
(_excludedLambdaMethods ??= []).AddRange(CollectLambdaMethodsInsideLocalFunction(method));
_excludedMethodSections ??= [];
AnalyzeCompileGeneratedTypesForExcludedMethod(method);
CacheExcludedMethodSection(method);
}
@@ -527,7 +524,7 @@ private void InstrumentMethod(MethodDefinition method)

if (!string.IsNullOrEmpty(sourceFile) && _excludedFilesHelper.Exclude(sourceFile))
{
if (!(_excludedSourceFiles ??= new List<string>()).Contains(sourceFile))
if (!(_excludedSourceFiles ??= []).Contains(sourceFile))
{
_excludedSourceFiles.Add(sourceFile);
}
@@ -642,8 +639,11 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
{
if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url), out Document document))
{
document = new Document { Path = _sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url) };
document.Index = _result.Documents.Count;
document = new Document
{
Path = _sourceRootTranslator.ResolveFilePath(sequencePoint.Document.Url),
Index = _result.Documents.Count
};
_result.Documents.Add(document.Path, document);
}

@@ -662,8 +662,11 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
{
if (!_result.Documents.TryGetValue(_sourceRootTranslator.ResolveFilePath(branchPoint.Document), out Document document))
{
document = new Document { Path = _sourceRootTranslator.ResolveFilePath(branchPoint.Document) };
document.Index = _result.Documents.Count;
document = new Document
{
Path = _sourceRootTranslator.ResolveFilePath(branchPoint.Document),
Index = _result.Documents.Count
};
_result.Documents.Add(document.Path, document);
}

@@ -688,7 +691,7 @@ private Instruction AddInstrumentationCode(MethodDefinition method, ILProcessor
{
if (_branchesInCompiledGeneratedClass == null)
{
_branchesInCompiledGeneratedClass = new List<string>();
_branchesInCompiledGeneratedClass = [];
}

if (!_branchesInCompiledGeneratedClass.Contains(method.FullName))
@@ -922,9 +925,7 @@ public ExcludedFilesHelper(string[] excludes, ILogger logger)
{
continue;
}
#pragma warning disable IDE0057 // Use range operator
_matcher.AddInclude(Path.IsPathRooted(excludeRule) ? excludeRule.Substring(Path.GetPathRoot(excludeRule).Length) : excludeRule);
#pragma warning restore IDE0057 // Use range operator
}
}
}
@@ -935,9 +936,7 @@ public bool Exclude(string sourceFile)
return false;

// We strip out drive because it doesn't work with globbing
#pragma warning disable IDE0057 // Use range operator
return _matcher.Match(Path.IsPathRooted(sourceFile) ? sourceFile.Substring(Path.GetPathRoot(sourceFile).Length) : sourceFile).HasMatches;
#pragma warning restore IDE0057 // Use range operator
}
}
}
2 changes: 0 additions & 2 deletions src/coverlet.core/Instrumentation/InstrumenterResult.cs
Original file line number Diff line number Diff line change
@@ -48,9 +48,7 @@ internal class BranchKey : IEquatable<BranchKey>
public int Line { get; set; }
[DataMember]
public int Ordinal { get; set; }

public override bool Equals(object obj) => Equals(obj);

public bool Equals(BranchKey other) => other is BranchKey branchKey && branchKey.Line == Line && branchKey.Ordinal == Ordinal;

public override int GetHashCode()
36 changes: 17 additions & 19 deletions src/coverlet.core/Reporters/CoberturaReporter.cs
Original file line number Diff line number Diff line change
@@ -25,13 +25,13 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
{
var summary = new CoverageSummary();

CoverageDetails lineCoverage = summary.CalculateLineCoverage(result.Modules);
CoverageDetails branchCoverage = summary.CalculateBranchCoverage(result.Modules);
CoverageDetails lineCoverage = CoverageSummary.CalculateLineCoverage(result.Modules);
CoverageDetails branchCoverage = CoverageSummary.CalculateBranchCoverage(result.Modules);

var xml = new XDocument();
var coverage = new XElement("coverage");
coverage.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture)));
coverage.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture)));
coverage.Add(new XAttribute("line-rate", (CoverageSummary.CalculateLineCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture)));
coverage.Add(new XAttribute("branch-rate", (CoverageSummary.CalculateBranchCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture)));
coverage.Add(new XAttribute("version", "1.9"));
coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds));

@@ -40,7 +40,7 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
var absolutePaths = new List<string>();
if (!result.Parameters.DeterministicReport)
{
absolutePaths = GetBasePaths(result.Modules, result.Parameters.UseSourceLink).ToList();
absolutePaths = [.. GetBasePaths(result.Modules, result.Parameters.UseSourceLink)];
absolutePaths.ForEach(x => sources.Add(new XElement("source", x)));
}

@@ -49,9 +49,9 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
{
var package = new XElement("package");
package.Add(new XAttribute("name", Path.GetFileNameWithoutExtension(module.Key)));
package.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
package.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
package.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(module.Value)));
package.Add(new XAttribute("line-rate", (CoverageSummary.CalculateLineCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
package.Add(new XAttribute("branch-rate", (CoverageSummary.CalculateBranchCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
package.Add(new XAttribute("complexity", CoverageSummary.CalculateCyclomaticComplexity(module.Value)));

var classes = new XElement("classes");
foreach (KeyValuePair<string, Classes> document in module.Value)
@@ -70,9 +70,9 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
fileName = sourceRootTranslator.ResolveDeterministicPath(document.Key);
}
@class.Add(new XAttribute("filename", fileName));
@class.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
@class.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
@class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value)));
@class.Add(new XAttribute("line-rate", (CoverageSummary.CalculateLineCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
@class.Add(new XAttribute("branch-rate", (CoverageSummary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture)));
@class.Add(new XAttribute("complexity", CoverageSummary.CalculateCyclomaticComplexity(cls.Value)));

var classLines = new XElement("lines");
var methods = new XElement("methods");
@@ -86,9 +86,9 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
var method = new XElement("method");
method.Add(new XAttribute("name", meth.Key.Split(':').Last().Split('(').First()));
method.Add(new XAttribute("signature", "(" + meth.Key.Split(':').Last().Split('(').Last()));
method.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(meth.Value.Lines).Percent / 100).ToString(CultureInfo.InvariantCulture)));
method.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(meth.Value.Branches).Percent / 100).ToString(CultureInfo.InvariantCulture)));
method.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(meth.Value.Branches)));
method.Add(new XAttribute("line-rate", (CoverageSummary.CalculateLineCoverage(meth.Value.Lines).Percent / 100).ToString(CultureInfo.InvariantCulture)));
method.Add(new XAttribute("branch-rate", (CoverageSummary.CalculateBranchCoverage(meth.Value.Branches).Percent / 100).ToString(CultureInfo.InvariantCulture)));
method.Add(new XAttribute("complexity", CoverageSummary.CalculateCyclomaticComplexity(meth.Value.Branches)));

var lines = new XElement("lines");
foreach (KeyValuePair<int, int> ln in meth.Value.Lines)
@@ -102,7 +102,7 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
if (isBranchPoint)
{
var branches = meth.Value.Branches.Where(b => b.Line == ln.Key).ToList();
CoverageDetails branchInfoCoverage = summary.CalculateBranchCoverage(branches);
CoverageDetails branchInfoCoverage = CoverageSummary.CalculateBranchCoverage(branches);
line.Add(new XAttribute("condition-coverage", $"{branchInfoCoverage.Percent.ToString(CultureInfo.InvariantCulture)}% ({branchInfoCoverage.Covered.ToString(CultureInfo.InvariantCulture)}/{branchInfoCoverage.Total.ToString(CultureInfo.InvariantCulture)})"));
var conditions = new XElement("conditions");
var byOffset = branches.GroupBy(b => b.Offset).ToDictionary(b => b.Key, b => b.ToList());
@@ -111,7 +111,7 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
var condition = new XElement("condition");
condition.Add(new XAttribute("number", entry.Key));
condition.Add(new XAttribute("type", entry.Value.Count > 2 ? "switch" : "jump")); // Just guessing here
condition.Add(new XAttribute("coverage", $"{summary.CalculateBranchCoverage(entry.Value).Percent.ToString(CultureInfo.InvariantCulture)}%"));
condition.Add(new XAttribute("coverage", $"{CoverageSummary.CalculateBranchCoverage(entry.Value).Percent.ToString(CultureInfo.InvariantCulture)}%"));
conditions.Add(condition);
}

@@ -179,7 +179,7 @@ private static IEnumerable<string> GetBasePaths(Modules modules, bool useSourceL
*/
if (useSourceLink)
{
return new[] { string.Empty };
return [string.Empty];
}

return modules.Values.SelectMany(k => k.Keys).GroupBy(Directory.GetDirectoryRoot).Select(group =>
@@ -224,9 +224,7 @@ private static string GetRelativePathFromBase(IEnumerable<string> basePaths, str
{
if (path.StartsWith(basePath))
{
#pragma warning disable IDE0057 // Use range operator
return path.Substring(basePath.Length);
#pragma warning restore IDE0057 // Use range operator
}
}
return path;
7 changes: 3 additions & 4 deletions src/coverlet.core/Reporters/LcovReporter.cs
Original file line number Diff line number Diff line change
@@ -23,16 +23,15 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
throw new NotSupportedException("Deterministic report not supported by lcov reporter");
}

var summary = new CoverageSummary();
var lcov = new List<string>();

foreach (KeyValuePair<string, Documents> module in result.Modules)
{
foreach (KeyValuePair<string, Classes> doc in module.Value)
{
CoverageDetails docLineCoverage = summary.CalculateLineCoverage(doc.Value);
CoverageDetails docBranchCoverage = summary.CalculateBranchCoverage(doc.Value);
CoverageDetails docMethodCoverage = summary.CalculateMethodCoverage(doc.Value);
CoverageDetails docLineCoverage = CoverageSummary.CalculateLineCoverage(doc.Value);
CoverageDetails docBranchCoverage = CoverageSummary.CalculateBranchCoverage(doc.Value);
CoverageDetails docMethodCoverage = CoverageSummary.CalculateMethodCoverage(doc.Value);

lcov.Add("SF:" + doc.Key);
foreach (KeyValuePair<string, Methods> @class in doc.Value)
30 changes: 15 additions & 15 deletions src/coverlet.core/Reporters/OpenCoverReporter.cs
Original file line number Diff line number Diff line change
@@ -77,10 +77,10 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
if (meth.Value.Lines.Count == 0)
continue;

CoverageDetails methLineCoverage = summary.CalculateLineCoverage(meth.Value.Lines);
CoverageDetails methBranchCoverage = summary.CalculateBranchCoverage(meth.Value.Branches);
int methCyclomaticComplexity = summary.CalculateCyclomaticComplexity(meth.Value.Branches);
int methNpathComplexity = summary.CalculateNpathComplexity(meth.Value.Branches);
CoverageDetails methLineCoverage = CoverageSummary.CalculateLineCoverage(meth.Value.Lines);
CoverageDetails methBranchCoverage = CoverageSummary.CalculateBranchCoverage(meth.Value.Branches);
int methCyclomaticComplexity = CoverageSummary.CalculateCyclomaticComplexity(meth.Value.Branches);
int methNpathComplexity = CoverageSummary.CalculateNpathComplexity(meth.Value.Branches);

var method = new XElement("Method");

@@ -122,8 +122,8 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran

foreach (System.Collections.Generic.KeyValuePair<int, int> lines in meth.Value.Lines)
{
BranchInfo[] lineBranches = meth.Value.Branches.Where(branchInfo => branchInfo.Line == lines.Key).ToArray();
CoverageDetails branchCoverage = summary.CalculateBranchCoverage(lineBranches);
BranchInfo[] lineBranches = [.. meth.Value.Branches.Where(branchInfo => branchInfo.Line == lines.Key)];
CoverageDetails branchCoverage = CoverageSummary.CalculateBranchCoverage(lineBranches);

var sequencePoint = new XElement("SequencePoint");
sequencePoint.Add(new XAttribute("vc", lines.Value.ToString()));
@@ -194,11 +194,11 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
if (classVisited)
visitedClasses++;

CoverageDetails classLineCoverage = summary.CalculateLineCoverage(cls.Value);
CoverageDetails classBranchCoverage = summary.CalculateBranchCoverage(cls.Value);
CoverageDetails classMethodCoverage = summary.CalculateMethodCoverage(cls.Value);
int classMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(cls.Value);
int classMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(cls.Value);
CoverageDetails classLineCoverage = CoverageSummary.CalculateLineCoverage(cls.Value);
CoverageDetails classBranchCoverage = CoverageSummary.CalculateBranchCoverage(cls.Value);
CoverageDetails classMethodCoverage = CoverageSummary.CalculateMethodCoverage(cls.Value);
int classMaxCyclomaticComplexity = CoverageSummary.CalculateMaxCyclomaticComplexity(cls.Value);
int classMinCyclomaticComplexity = CoverageSummary.CalculateMinCyclomaticComplexity(cls.Value);

classSummary.Add(new XAttribute("numSequencePoints", classLineCoverage.Total.ToString()));
classSummary.Add(new XAttribute("visitedSequencePoints", classLineCoverage.Covered.ToString()));
@@ -226,10 +226,10 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
modules.Add(module);
}

CoverageDetails moduleLineCoverage = summary.CalculateLineCoverage(result.Modules);
CoverageDetails moduleBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
int moduleMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(result.Modules);
int moduleMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(result.Modules);
CoverageDetails moduleLineCoverage = CoverageSummary.CalculateLineCoverage(result.Modules);
CoverageDetails moduleBranchCoverage = CoverageSummary.CalculateBranchCoverage(result.Modules);
int moduleMaxCyclomaticComplexity = CoverageSummary.CalculateMaxCyclomaticComplexity(result.Modules);
int moduleMinCyclomaticComplexity = CoverageSummary.CalculateMinCyclomaticComplexity(result.Modules);

coverageSummary.Add(new XAttribute("numSequencePoints", moduleLineCoverage.Total.ToString()));
coverageSummary.Add(new XAttribute("visitedSequencePoints", moduleLineCoverage.Covered.ToString()));
7 changes: 3 additions & 4 deletions src/coverlet.core/Reporters/TeamCityReporter.cs
Original file line number Diff line number Diff line change
@@ -24,10 +24,9 @@ public string Report(CoverageResult result, ISourceRootTranslator sourceRootTran
}

// Calculate coverage
var summary = new CoverageSummary();
CoverageDetails overallLineCoverage = summary.CalculateLineCoverage(result.Modules);
CoverageDetails overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules);
CoverageDetails overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules);
CoverageDetails overallLineCoverage = CoverageSummary.CalculateLineCoverage(result.Modules);
CoverageDetails overallBranchCoverage = CoverageSummary.CalculateBranchCoverage(result.Modules);
CoverageDetails overallMethodCoverage = CoverageSummary.CalculateMethodCoverage(result.Modules);

// Report coverage
var stringBuilder = new StringBuilder();
26 changes: 13 additions & 13 deletions src/coverlet.core/Symbols/CecilSymbolHelper.cs
Original file line number Diff line number Diff line change
@@ -491,7 +491,7 @@ private bool SkipGeneratedBranchesForExceptionHandlers(MethodDefinition methodDe
}
}

_compilerGeneratedBranchesToExclude.TryAdd(methodDefinition.FullName, detectedBranches.ToArray());
_compilerGeneratedBranchesToExclude.TryAdd(methodDefinition.FullName, [.. detectedBranches]);
}

return _compilerGeneratedBranchesToExclude[methodDefinition.FullName].Contains(instruction.Offset);
@@ -722,7 +722,7 @@ static bool CheckForSkipDisposal(List<Instruction> instructions, Instruction ins

if (instructions[currentIndex - 1].OpCode == OpCodes.Ldfld)
{
if(! IsCompilerGeneratedField(instructions[currentIndex - 1], out FieldDefinition field)) return false;
if (!IsCompilerGeneratedField(instructions[currentIndex - 1], out FieldDefinition field)) return false;

int maxReloadFieldIndex = Math.Min(currentIndex + 2, instructions.Count - 2);

@@ -1119,7 +1119,7 @@ private static bool BuildPointsForConditionalBranch(List<BranchPoint> list, Inst
OffsetPoints =
pathOffsetList.Count > 1
? pathOffsetList.GetRange(0, pathOffsetList.Count - 1)
: new List<int>(),
: [],
EndOffset = pathOffsetList.Last()
};

@@ -1160,26 +1160,26 @@ private static uint BuildPointsForBranch(List<BranchPoint> list, Instruction the
OffsetPoints =
pathOffsetList1.Count > 1
? pathOffsetList1.GetRange(0, pathOffsetList1.Count - 1)
: new List<int>(),
: [],
EndOffset = pathOffsetList1.Last()
};

// only add branch if branch does not match a known sequence
// e.g. auto generated field assignment
// or encapsulates at least one sequence point
int[] offsets = new[]
{
int[] offsets =
[
path0.Offset,
path0.EndOffset,
path1.Offset,
path1.EndOffset
};
];

Code[][] ignoreSequences = new[]
{
Code[][] ignoreSequences =
[
// we may need other samples
new[] {Code.Brtrue_S, Code.Pop, Code.Ldsfld, Code.Ldftn, Code.Newobj, Code.Dup, Code.Stsfld, Code.Newobj}, // CachedAnonymousMethodDelegate field allocation
};
[Code.Brtrue_S, Code.Pop, Code.Ldsfld, Code.Ldftn, Code.Newobj, Code.Dup, Code.Stsfld, Code.Newobj], // CachedAnonymousMethodDelegate field allocation
];

int bs = offsets.Min();
int be = offsets.Max();
@@ -1218,7 +1218,7 @@ private static uint BuildPointsForSwitchCases(List<BranchPoint> list, BranchPoin
OffsetPoints =
pathOffsetList1.Count > 1
? pathOffsetList1.GetRange(0, pathOffsetList1.Count - 1)
: new List<int>(),
: [],
EndOffset = pathOffsetList1.Last()
}));
pathCounter = counter;
@@ -1351,7 +1351,7 @@ private bool SkipExpressionBreakpointsSequences(MethodDefinition methodDefinitio
{
if (!_sequencePointOffsetToSkip.ContainsKey(methodDefinition.FullName))
{
_sequencePointOffsetToSkip.TryAdd(methodDefinition.FullName, new List<int>());
_sequencePointOffsetToSkip.TryAdd(methodDefinition.FullName, []);
}
_sequencePointOffsetToSkip[methodDefinition.FullName].Add(instruction.Offset);
_sequencePointOffsetToSkip[methodDefinition.FullName].Add(instruction.Next.Offset);
16 changes: 4 additions & 12 deletions src/coverlet.core/coverlet.core.csproj
Original file line number Diff line number Diff line change
@@ -2,7 +2,7 @@

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
<IsPackable>false</IsPackable>
</PropertyGroup>

@@ -14,20 +14,12 @@
<PackageReference Include="Mono.Cecil" />
<PackageReference Include="NuGet.Versioning" />
<PackageReference Include="Newtonsoft.Json" />
<PackageReference Include="System.Buffers" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Text.Json" VersionOverride="6.0.11" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' != 'netstandard2.0'">
<PackageReference Include="System.Reflection.Metadata" VersionOverride="6.0.2" />
<PackageReference Include="System.Collections.Immutable" VersionOverride="6.0.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="System.Reflection.Metadata" VersionOverride="$(SystemReflectionMetadataVersion)" />
<PackageReference Include="System.Collections.Immutable" VersionOverride="$(SystemCollectionsImmutableVersion)" />
<PackageReference Include="System.Buffers" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
</ItemGroup>

</Project>
23 changes: 11 additions & 12 deletions src/coverlet.msbuild.tasks/CoverageResultTask.cs
Original file line number Diff line number Diff line change
@@ -104,7 +104,7 @@ public override bool Execute()
IReporter reporter = new ReporterFactory(format).CreateReporter();
if (reporter == null)
{
throw new Exception($"Specified output format '{format}' is not supported");
throw new ArgumentException($"Specified output format '{format}' is not supported");
}

if (reporter.OutputType == ReporterOutputType.Console)
@@ -155,7 +155,7 @@ public override bool Execute()
IEnumerable<string> thresholdValues = Threshold.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim());
if (thresholdValues.Count() != thresholdTypeFlagQueue.Count)
{
throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match");
throw new InvalidOperationException($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match");
}

foreach (string threshold in thresholdValues)
@@ -166,7 +166,7 @@ public override bool Execute()
}
else
{
throw new Exception($"Invalid threshold value must be numeric");
throw new ArgumentException($"Invalid threshold value must be numeric");
}
}
}
@@ -191,11 +191,10 @@ public override bool Execute()
}

var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method");
var summary = new CoverageSummary();

CoverageDetails linePercentCalculation = summary.CalculateLineCoverage(result.Modules);
CoverageDetails branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules);
CoverageDetails methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules);
CoverageDetails linePercentCalculation = CoverageSummary.CalculateLineCoverage(result.Modules);
CoverageDetails branchPercentCalculation = CoverageSummary.CalculateBranchCoverage(result.Modules);
CoverageDetails methodPercentCalculation = CoverageSummary.CalculateMethodCoverage(result.Modules);

double totalLinePercent = linePercentCalculation.Percent;
double totalBranchPercent = branchPercentCalculation.Percent;
@@ -207,9 +206,9 @@ public override bool Execute()

foreach (KeyValuePair<string, Documents> module in result.Modules)
{
double linePercent = summary.CalculateLineCoverage(module.Value).Percent;
double branchPercent = summary.CalculateBranchCoverage(module.Value).Percent;
double methodPercent = summary.CalculateMethodCoverage(module.Value).Percent;
double linePercent = CoverageSummary.CalculateLineCoverage(module.Value).Percent;
double branchPercent = CoverageSummary.CalculateBranchCoverage(module.Value).Percent;
double methodPercent = CoverageSummary.CalculateMethodCoverage(module.Value).Percent;

coverageTable.AddRow(Path.GetFileNameWithoutExtension(module.Key), $"{InvariantFormat(linePercent)}%", $"{InvariantFormat(branchPercent)}%", $"{InvariantFormat(methodPercent)}%");
}
@@ -226,7 +225,7 @@ public override bool Execute()

Console.WriteLine(coverageTable.ToStringAlternative());

ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStat);
ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(thresholdTypeFlagValues, thresholdStat);
if (thresholdTypeFlags != ThresholdTypeFlags.None)
{
var exceptionMessageBuilder = new StringBuilder();
@@ -248,7 +247,7 @@ public override bool Execute()
$"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}");
}

throw new Exception(exceptionMessageBuilder.ToString());
throw new InvalidOperationException(exceptionMessageBuilder.ToString());
}
}
catch (Exception ex)
2 changes: 1 addition & 1 deletion src/coverlet.msbuild.tasks/InstrumentationTask.cs
Original file line number Diff line number Diff line change
@@ -57,7 +57,7 @@ public InstrumentationTask()
_logger = new MSBuildLogger(Log);
}

private void AttachDebugger()
private static void AttachDebugger()
{
if (int.TryParse(Environment.GetEnvironmentVariable("COVERLET_MSBUILD_INSTRUMENTATIONTASK_DEBUG"), out int result) && result == 1)
{
5 changes: 4 additions & 1 deletion src/coverlet.msbuild.tasks/coverlet.msbuild.props
Original file line number Diff line number Diff line change
@@ -18,7 +18,10 @@
<ThresholdStat Condition="$(ThresholdStat) == ''">minimum</ThresholdStat>
<ExcludeAssembliesWithoutSources Condition="$(ExcludeAssembliesWithoutSources) == ''"></ExcludeAssembliesWithoutSources>
</PropertyGroup>
<PropertyGroup>
<PropertyGroup Condition=" '$(OS)' == 'Windows_NT' ">
<CoverletToolsPath Condition=" '$(CoverletToolsPath)' == '' ">$(MSBuildThisFileDirectory)..\tasks\netstandard2.0\</CoverletToolsPath>
</PropertyGroup>
<PropertyGroup Condition=" '$(OS)' != 'Windows_NT' ">
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry I don't understand this and it looks like something that could potentially lead to an issue where I'm searching for hours and hours to find it 😃 . Why we need this condition here?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a small usability update. I use windows OS and was tired to update the string manually after the message with the path was written to the console.

<CoverletToolsPath Condition=" '$(CoverletToolsPath)' == '' ">$(MSBuildThisFileDirectory)../tasks/netstandard2.0/</CoverletToolsPath>
</PropertyGroup>
</Project>
13 changes: 3 additions & 10 deletions src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFrameworks>netstandard2.0</TargetFrameworks>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyTitle>coverlet.msbuild.tasks</AssemblyTitle>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<TargetsForTfmSpecificContentInPackage>$(TargetsForTfmSpecificContentInPackage);PackBuildOutputs</TargetsForTfmSpecificContentInPackage>
@@ -41,19 +41,12 @@

<ItemGroup>
<PackageReference Include="Microsoft.Build.Utilities.Core" VersionOverride="$(MicrosoftBuildUtilitiesCorePackageVersion)" PrivateAssets="all" ExcludeAssets="Runtime" />
<PackageReference Include="System.Buffers" />
<PackageReference Include="System.Memory" />
<PackageReference Include="System.Threading.Tasks.Extensions" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)..\coverlet.core\coverlet.core.csproj" PrivateAssets="all" />
</ItemGroup>

<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)..\coverlet.console\ConsoleTables\ConsoleTable.cs" Link="ConsoleTables\ConsoleTable.cs" />
</ItemGroup>

<ItemGroup>
<None Include="coverlet.msbuild.props" Pack="true" PackagePath="build\">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
@@ -67,7 +60,7 @@
</None>
</ItemGroup>

<Target Name="PackBuildOutputs">
<Target Name="PackBuildOutputs" DependsOnTargets="BuildOnlySettings;ResolveReferences">
<ItemGroup>
<TfmSpecificPackageFile Include="$(TargetPath)" PackagePath="tasks\$(TargetFramework)" />
<TfmSpecificPackageFile Include="$(ProjectDepsFilePath)" PackagePath="tasks\$(TargetFramework)" />
3 changes: 2 additions & 1 deletion test/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -14,7 +14,8 @@
This is required when the coverlet.msbuild imports are made in their src directory
(so that msbuild eval works even before they are built)
so that they can still find the tooling that will be built by the build. -->
<CoverletToolsPath>$(RepoRoot)artifacts\bin\coverlet.msbuild.tasks\$(Configuration.ToLowerInvariant())_netstandard2.0\</CoverletToolsPath>
<!--<CoverletToolsPath>$(RepoRoot)artifacts\bin\coverlet.msbuild.tasks\$(Configuration.ToLowerInvariant())_netstandard2.0\</CoverletToolsPath>-->
<CoverletToolsPath>$(RepoRoot)artifacts\bin\coverlet.msbuild.tasks\$(Configuration.ToLower())\</CoverletToolsPath>
</PropertyGroup>
</When>
</Choose>
59 changes: 59 additions & 0 deletions test/coverlet.collector.tests/CoverletInProcDataCollectorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Copyright (c) Toni Solarin-Sodara
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Reflection;
using Coverlet.Collector.DataCollection;
using Microsoft.VisualStudio.TestPlatform.ObjectModel.DataCollection;
using Moq;
using Xunit;

namespace Coverlet.Collector.Tests.DataCollection
{
public class CoverletInProcDataCollectorTests
{
private readonly CoverletInProcDataCollector _dataCollector;

public CoverletInProcDataCollectorTests()
{
_dataCollector = new CoverletInProcDataCollector();
}

[Fact]
public void GetInstrumentationClass_ShouldReturnNullForNonMatchingType_EnabledLogging()
{
// Arrange
var dataCollectionSink = new Mock<IDataCollectionSink>();
var mockAssembly = new Mock<Assembly>();
var mockType = new Mock<Type>();
mockType.Setup(t => t.Namespace).Returns("Coverlet.Core.Instrumentation.Tracker");
mockType.Setup(t => t.Name).Returns("MockAssembly_Tracker");
mockAssembly.Setup(a => a.GetTypes()).Returns(new[] { mockType.Object });
Environment.SetEnvironmentVariable("COVERLET_DATACOLLECTOR_INPROC_EXCEPTIONLOG_ENABLED", "1");
_dataCollector.Initialize(dataCollectionSink.Object);

// Act & Assert
var results = _dataCollector.GetInstrumentationClass(mockAssembly.Object);

// Assert
Assert.Null(results);
}

[Fact]
public void GetInstrumentationClass_ShouldReturnNullForNonMatchingType()
{
// Arrange
var mockAssembly = new Mock<Assembly>();
var mockType = new Mock<Type>();
mockType.Setup(t => t.Namespace).Returns("NonMatchingNamespace");
mockType.Setup(t => t.Name).Returns("NonMatchingName");
mockAssembly.Setup(a => a.GetTypes()).Returns(new[] { mockType.Object });

// Act
var result = _dataCollector.GetInstrumentationClass(mockAssembly.Object);

// Assert
Assert.Null(result);
}
}
}
Loading