diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f79ffe598..dd234c79f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -2,6 +2,11 @@
Contributions are highly welcome, however, except for very small changes, kindly file an issue and let's have a discussion before you open a pull request.
+## Requirements
+
+.NET SDK 2.2 https://dotnet.microsoft.com/download/dotnet-core/2.2
+.NET SDK 3.1 https://dotnet.microsoft.com/download/dotnet-core/3.1
+
## Building the Project
Clone this repo:
diff --git a/Directory.Build.props b/Directory.Build.props
index 99cdb5278..d40cabc6e 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -4,11 +4,12 @@
$(MSBuildThisFileDirectory)
Debug
$(MSBuildThisFileDirectory)bin\$(Configuration)\Packages\
-
true
true
true
snupkg
+ true
+ preview
diff --git a/Documentation/Changelog.md b/Documentation/Changelog.md
index 6ad878910..bb8777845 100644
--- a/Documentation/Changelog.md
+++ b/Documentation/Changelog.md
@@ -6,6 +6,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
+### Fixed
+
+-Fixed ExcludeFromCodeCoverage attribute bugs [#129](https://github.com/tonerdo/coverlet/issues/129) and [#670](https://github.com/tonerdo/coverlet/issues/670) with [#671](https://github.com/tonerdo/coverlet/pull/671) by https://github.com/matteoerigozzi
+
+### Improvements
+
+-Trim whitespace between values when reading from configuration from runsettings [#679](https://github.com/tonerdo/coverlet/pull/679) by https://github.com/EricStG
+
+## Release date 2020-01-03
+### Packages
+coverlet.msbuild 2.8.0
+coverlet.console 1.7.0
+coverlet.collector 1.2.0
+
### Added
-Add log to tracker [#553](https://github.com/tonerdo/coverlet/pull/553)
-Exclude by assembly level System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage [#589](https://github.com/tonerdo/coverlet/pull/589)
@@ -24,7 +38,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Improvements
--Improve exception message for unsupported runtime [#569](https://github.com/tonerdo/coverlet/pull/569) by https://github.com/daveMueller
+-Improve exception message for unsupported runtime [#569](https://github.com/tonerdo/
+coverlet/pull/569) by https://github.com/daveMueller
+-Improve cobertura absolute/relative path report generation [#661](https://github.com/tonerdo/coverlet/pull/661) by https://github.com/daveMueller
## Release date 2019-09-23
### Packages
diff --git a/Documentation/Examples/MSBuild/MergeWith/HowTo.md b/Documentation/Examples/MSBuild/MergeWith/HowTo.md
index ef5b66205..d975d8f2b 100644
--- a/Documentation/Examples/MSBuild/MergeWith/HowTo.md
+++ b/Documentation/Examples/MSBuild/MergeWith/HowTo.md
@@ -7,4 +7,11 @@ Last command will join and create final needed format file.
dotnet test XUnitTestProject1\XUnitTestProject1.csproj /p:CollectCoverage=true /p:CoverletOutput=../CoverageResults/
dotnet test XUnitTestProject2\XUnitTestProject2.csproj /p:CollectCoverage=true /p:CoverletOutput=../CoverageResults/ /p:MergeWith="../CoverageResults/coverage.json"
dotnet test XUnitTestProject3\XUnitTestProject3.csproj /p:CollectCoverage=true /p:CoverletOutput=../CoverageResults/ /p:MergeWith="../CoverageResults/coverage.json" /p:CoverletOutputFormat="opencover"
-```
\ No newline at end of file
+```
+
+You can merge also running `dotnet test` and merge with single command from a solution file, but you need to ensure that tests will run sequentially(`-m:1`). This slow down testing but avoid invalid coverage result.
+
+```
+dotnet test /p:CollectCoverage=true /p:CoverletOutput=../CoverageResults/ /p:MergeWith="../CoverageResults/coverage.json" /p:CoverletOutputFormat=\"opencover,json\" -m:1
+```
+N.B. You need to specify `json` format plus another format(the final one), because Coverlet can only merge proprietary format. At the end you can delete temporary `coverage.json` file.
\ No newline at end of file
diff --git a/Documentation/ReleasePlan.md b/Documentation/ReleasePlan.md
index 1479dd693..fdfd7a513 100644
--- a/Documentation/ReleasePlan.md
+++ b/Documentation/ReleasePlan.md
@@ -28,9 +28,9 @@ We plan 1 release [once per quarter](https://en.wikipedia.org/wiki/Calendar_year
| Package | **coverlet.msbuild** |
| :-------------: |:-------------:|
-|**coverlet.msbuild** | 2.7.0 |
-|**coverlet.console** | 1.6.0 |
-|**coverlet.collector** | 1.1.0 |
+|**coverlet.msbuild** | 2.8.0 |
+|**coverlet.console** | 1.7.0 |
+|**coverlet.collector** | 1.2.0 |
### Proposed next versions
@@ -41,11 +41,59 @@ We MANUALLY bump versions on production release, so we have different release pl
| Release Date | **coverlet.msbuild** | **coverlet.console** | **coverlet.collector** | **commit hash**| **notes** |
| :-------------: |:-------------:|:-------------:|:-------------:|:-------------:|:-------------:|
+| <01 April 2020> | 2.8.1 | 1.7.1 | 1.2.1 |
+| 03 January 2019 | 2.8.0 | 1.7.0 | 1.2.0 | 72a688f1c47fa92059540d5fbb1c4b0b4bf0dc8c | |
| 23 September 2019 | 2.7.0 | 1.6.0 | 1.1.0 | 4ca01eb239038808739699470a61fad675af6c79 | |
-| 1 July 2019 | 2.6.3 | 1.5.3 | 1.0.1 | e1593359497fdfe6befbb86304b8f4e09a656d14 | |
-| 6 June 2019 | 2.6.2 | 1.5.2 | 1.0.0 | 3e7eac9df094c22335711a298d359890aed582e8 | first collector release |
+| 01 July 2019 | 2.6.3 | 1.5.3 | 1.0.1 | e1593359497fdfe6befbb86304b8f4e09a656d14 | |
+| 06 June 2019 | 2.6.2 | 1.5.2 | 1.0.0 | 3e7eac9df094c22335711a298d359890aed582e8 | first collector release |
+
+*< date > Expected next release date
To get the list of commits between two version use git command
```bash
git log --oneline hashbefore currenthash
-```
\ No newline at end of file
+```
+
+# How to manually release packages to Nuget.org
+
+This is the steps to do to release new packages to Nuget.org
+
+1) Clone repo, **remember to build packages from master and not from your fork or metadata links will point to your forked repo.**
+Run `git log -5` from repo root to verify last commit.
+
+2) Update project versions in file:
+
+Collector
+https://github.com/tonerdo/coverlet/blob/master/src/coverlet.collector/version.json
+.NET tool
+https://github.com/tonerdo/coverlet/blob/master/src/coverlet.console/version.json
+Msbuild tasks
+https://github.com/tonerdo/coverlet/blob/master/src/coverlet.msbuild.tasks/version.json
+
+Core lib project file https://github.com/tonerdo/coverlet/blob/master/src/coverlet.core/coverlet.core.csproj.
+The version of core lib project file is the version we'll report on github repo releases https://github.com/tonerdo/coverlet/releases
+
+
+Sample of updated version PR https://github.com/tonerdo/coverlet/pull/675/files
+
+3) From new cloned, aligned and versions updated repo root run pack command
+```
+dotnet pack -c release /p:PublicRelease=true
+...
+ coverlet.console -> D:\git\coverlet\src\coverlet.console\bin\Release\netcoreapp2.2\coverlet.console.dll
+ coverlet.console -> D:\git\coverlet\src\coverlet.console\bin\Release\netcoreapp2.2\publish\
+ Successfully created package 'D:\git\coverlet\bin\Release\Packages\coverlet.msbuild.2.8.1.nupkg'.
+ Successfully created package 'D:\git\coverlet\bin\Release\Packages\coverlet.msbuild.2.8.1.snupkg'.
+ Successfully created package 'D:\git\coverlet\bin\Release\Packages\coverlet.console.1.7.1.nupkg'.
+ Successfully created package 'D:\git\coverlet\bin\Release\Packages\coverlet.console.1.7.1.snupkg'.
+ Successfully created package 'D:\git\coverlet\bin\Release\Packages\coverlet.collector.1.2.1.nupkg'.
+ Successfully created package 'D:\git\coverlet\bin\Release\Packages\coverlet.collector.1.2.1.snupkg'.
+```
+
+4) Upload *.nupkg files to Nuget.org site. **Check all metadata(url links etc...) before "Submit"**
+
+5) **On your fork**:
+* Align to master
+* Update versions in files accordingly to new release and commit/merge to master
+* Create release on repo https://github.com/tonerdo/coverlet/releases using https://github.com/tonerdo/coverlet/blob/master/src/coverlet.core/coverlet.core.csproj assembly version
+* Update the [Release Plan](https://github.com/tonerdo/coverlet/blob/master/Documentation/ReleasePlan.md)(this document) and [ChangeLog](https://github.com/tonerdo/coverlet/blob/master/Documentation/Changelog.md)
\ No newline at end of file
diff --git a/Documentation/Troubleshooting.md b/Documentation/Troubleshooting.md
index 0a5e47415..e78afbc9a 100644
--- a/Documentation/Troubleshooting.md
+++ b/Documentation/Troubleshooting.md
@@ -235,4 +235,12 @@ You'll get this message during test run
dotnet test -p:Include="[test_coverage.]" -p:Exclude="[*.Test.*]*" -p:CollectCoverage=true -p:CoverletOutputFormat=cobertura -p:CoverletOutput=coverage.cobertura.xml
Coverlet msbuild instrumentation task debugging is enabled. Please attach debugger to process to continue
Process Id: 29228 Name: dotnet
-```
\ No newline at end of file
+```
+
+## Enable collector instrumentation debugging
+
+You can live attach and debug collectors with `COVERLET_DATACOLLECTOR_OUTOFPROC_DEBUG` env variable
+```
+ set COVERLET_DATACOLLECTOR_OUTOFPROC_DEBUG=1
+```
+You will be asket to attach a debugger through UI popup.
\ No newline at end of file
diff --git a/Documentation/VSTestIntegration.md b/Documentation/VSTestIntegration.md
index 1ade115c8..97d9c09c6 100644
--- a/Documentation/VSTestIntegration.md
+++ b/Documentation/VSTestIntegration.md
@@ -30,13 +30,13 @@ These are a list of options that are supported by coverlet. These can be specifi
| Option | Summary |
|------------- |------------------------------------------------------------------------------------------|
|Format | Coverage output format. These are either cobertura, json, lcov, opencover or teamcity as well as combinations of these formats. |
-|MergeWith | Combine the output of multiple coverage runs into a single result([check the sample](Examples.md)). |
|Exclude | Exclude from code coverage analysing using filter expressions. |
|ExcludeByFile | Ignore specific source files from code coverage. |
|Include | Explicitly set what to include in code coverage analysis using filter expressions. |
|IncludeDirectory| Explicitly set which directories to include in code coverage analysis. |
|SingleHit | Specifies whether to limit code coverage hit reporting to a single hit for each location.|
|UseSourceLink | Specifies whether to use SourceLink URIs in place of file system paths. |
+|IncludeTestAssembly | Include coverage of the test assembly. |
How to specify these options via runsettings?
```
@@ -46,8 +46,7 @@ How to specify these options via runsettings?
- json,cobertura
- /custom/path/result.json
+ json,cobertura
[coverlet.*.tests?]*,[*]Coverlet.Core*
[coverlet.*]*,[*]Coverlet.Core*
Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute
@@ -55,6 +54,7 @@ How to specify these options via runsettings?
../dir1/,../dir2/,
false
true
+ true
@@ -67,6 +67,8 @@ This runsettings file can easily be provided using command line option as given
2. `dotnet vstest --settings coverletArgs.runsettings`
+Take a look at our [`HelloWorld`](Examples/VSTest/HelloWorld/HowTo.md) sample.
+
## Implementation Details
The proposed solution is implemented with the help of [datacollectors](https://github.com/Microsoft/vstest-docs/blob/master/docs/extensions/datacollector.md).
diff --git a/README.md b/README.md
index ac05e94a0..d2698ac08 100644
--- a/README.md
+++ b/README.md
@@ -24,7 +24,7 @@ Coverlet can be used through three different *drivers*
### VSTest Integration (preferred due to [know issue](https://github.com/tonerdo/coverlet/blob/master/Documentation/KnowIssues.md#1-vstest-stops-process-execution-earlydotnet-test))
-### Insallation
+### Installation
```bash
dotnet add package coverlet.collector
```
@@ -51,7 +51,7 @@ See [documentation](Documentation/VSTestIntegration.md) for advanced usage.
* Important [know issue](Documentation/KnowIssues.md#2-upgrade-coverletcollector-to-version--100)
### MSBuild Integration
-### Insallation
+### Installation
```bash
dotnet add package coverlet.msbuild
```
diff --git a/_assets/coverlet-icon.png b/_assets/coverlet-icon.png
new file mode 100644
index 000000000..fb062f307
Binary files /dev/null and b/_assets/coverlet-icon.png differ
diff --git a/eng/build.yml b/eng/build.yml
index dfc587262..e12b5069a 100644
--- a/eng/build.yml
+++ b/eng/build.yml
@@ -1,7 +1,12 @@
steps:
- task: UseDotNet@2
inputs:
- version: 2.2.402
+ version: 2.2.207
+ displayName: Install .NET Core SDK
+
+- task: UseDotNet@2
+ inputs:
+ version: 3.1.100
displayName: Install .NET Core SDK
- script: dotnet restore
@@ -10,7 +15,7 @@ steps:
- script: dotnet build -c $(BuildConfiguration) --no-restore
displayName: Build
-- script: dotnet pack -c $(BuildConfiguration) --no-build
+- script: dotnet pack -c $(BuildConfiguration)
displayName: Pack
- task: DotNetCoreCLI@2
@@ -18,4 +23,4 @@ steps:
inputs:
command: test
arguments: -c $(BuildConfiguration) --no-build /p:CollectCoverage=true /p:CoverletOutputFormat=opencover /p:Include=[coverlet.*]* /p:Exclude=[coverlet.tests.remoteexecutor]*
- testRunTitle: $(Agent.JobName)
+ testRunTitle: $(Agent.JobName)
\ No newline at end of file
diff --git a/global.json b/global.json
index 323ede7fb..e9aac8c22 100644
--- a/global.json
+++ b/global.json
@@ -1,5 +1,5 @@
{
- "sdk": {
- "version": "2.2.402"
- }
-}
\ No newline at end of file
+ "sdk": {
+ "version": "3.1.100"
+ }
+}
diff --git a/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs b/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs
index 9004fc292..d77fcfcf2 100644
--- a/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs
+++ b/src/coverlet.collector/DataCollection/CoverletCoverageCollector.cs
@@ -37,6 +37,15 @@ internal CoverletCoverageCollector(TestPlatformEqtTrace eqtTrace, ICoverageWrapp
_countDownEventFactory = countDownEventFactory;
}
+ private void AttachDebugger()
+ {
+ if (int.TryParse(Environment.GetEnvironmentVariable("COVERLET_DATACOLLECTOR_OUTOFPROC_DEBUG"), out int result) && result == 1)
+ {
+ Debugger.Launch();
+ Debugger.Break();
+ }
+ }
+
///
/// Initializes data collector
///
@@ -52,6 +61,9 @@ public override void Initialize(
DataCollectionLogger logger,
DataCollectionEnvironmentContext environmentContext)
{
+
+ AttachDebugger();
+
if (_eqtTrace.IsInfoEnabled)
{
_eqtTrace.Info("Initializing {0} with configuration: '{1}'", CoverletConstants.DataCollectorName, configurationElement?.OuterXml);
diff --git a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs
index 705a334f9..cbaaca3d1 100644
--- a/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs
+++ b/src/coverlet.collector/DataCollection/CoverletSettingsParser.cs
@@ -86,8 +86,7 @@ private string[] ParseReportFormats(XmlElement configurationElement)
if (configurationElement != null)
{
XmlElement reportFormatElement = configurationElement[CoverletConstants.ReportFormatElementName];
- formats = reportFormatElement?.InnerText?.Split(',').Select(format => format.Trim())
- .Where(format => !string.IsNullOrEmpty(format)).ToArray();
+ formats = this.SplitElement(reportFormatElement);
}
return formats is null || formats.Length == 0 ? new[] { CoverletConstants.DefaultReportFormat } : formats;
@@ -101,7 +100,7 @@ private string[] ParseReportFormats(XmlElement configurationElement)
private string[] ParseIncludeFilters(XmlElement configurationElement)
{
XmlElement includeFiltersElement = configurationElement[CoverletConstants.IncludeFiltersElementName];
- return includeFiltersElement?.InnerText?.Split(',');
+ return this.SplitElement(includeFiltersElement);
}
///
@@ -112,7 +111,7 @@ private string[] ParseIncludeFilters(XmlElement configurationElement)
private string[] ParseIncludeDirectories(XmlElement configurationElement)
{
XmlElement includeDirectoriesElement = configurationElement[CoverletConstants.IncludeDirectoriesElementName];
- return includeDirectoriesElement?.InnerText?.Split(',');
+ return this.SplitElement(includeDirectoriesElement);
}
///
@@ -127,7 +126,7 @@ private string[] ParseExcludeFilters(XmlElement configurationElement)
if (configurationElement != null)
{
XmlElement excludeFiltersElement = configurationElement[CoverletConstants.ExcludeFiltersElementName];
- string[] filters = excludeFiltersElement?.InnerText?.Split(',');
+ string[] filters = this.SplitElement(excludeFiltersElement);
if (filters != null)
{
excludeFilters.AddRange(filters);
@@ -145,7 +144,7 @@ private string[] ParseExcludeFilters(XmlElement configurationElement)
private string[] ParseExcludeSourceFiles(XmlElement configurationElement)
{
XmlElement excludeSourceFilesElement = configurationElement[CoverletConstants.ExcludeSourceFilesElementName];
- return excludeSourceFilesElement?.InnerText?.Split(',');
+ return this.SplitElement(excludeSourceFilesElement);
}
///
@@ -156,7 +155,7 @@ private string[] ParseExcludeSourceFiles(XmlElement configurationElement)
private string[] ParseExcludeAttributes(XmlElement configurationElement)
{
XmlElement excludeAttributesElement = configurationElement[CoverletConstants.ExcludeAttributesElementName];
- return excludeAttributesElement?.InnerText?.Split(',');
+ return this.SplitElement(excludeAttributesElement);
}
///
@@ -205,5 +204,15 @@ private bool ParseIncludeTestAssembly(XmlElement configurationElement)
bool.TryParse(includeTestAssemblyElement?.InnerText, out bool includeTestAssembly);
return includeTestAssembly;
}
+
+ ///
+ /// Splits a comma separated elements into an array
+ ///
+ /// The element to split
+ /// An array of the values in the element
+ private string[] SplitElement(XmlElement element)
+ {
+ return element?.InnerText?.Split(',', StringSplitOptions.RemoveEmptyEntries).Where(value => !string.IsNullOrWhiteSpace(value)).Select(value => value.Trim()).ToArray();
+ }
}
}
diff --git a/src/coverlet.collector/coverlet.collector.csproj b/src/coverlet.collector/coverlet.collector.csproj
index d1fc144ec..b23f2a210 100644
--- a/src/coverlet.collector/coverlet.collector.csproj
+++ b/src/coverlet.collector/coverlet.collector.csproj
@@ -1,22 +1,35 @@
-
+
netcoreapp2.0
coverlet.collector
- coverlet.collector
+ true
+ true
+ false
+
+ true
+ $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs
+
+ $(NoWarn);NU5127
+
+
+
+
coverlet.collector
+ coverlet.collector
tonerdo
MIT
http://github.com/tonerdo/coverlet
https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true
+ coverlet-icon.png
false
- true
Coverlet is a cross platform code coverage library for .NET, with support for line, branch and method coverage.
coverage testing unit-test lcov opencover quality
git
- true
- false
- true
- $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs
@@ -25,6 +38,7 @@
+
diff --git a/src/coverlet.collector/version.json b/src/coverlet.collector/version.json
index e315416f7..95095d4ef 100644
--- a/src/coverlet.collector/version.json
+++ b/src/coverlet.collector/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "1.1",
+ "version": "1.2",
"publicReleaseRefSpec": [
"^refs/heads/master$"
]
diff --git a/src/coverlet.console/coverlet.console.csproj b/src/coverlet.console/coverlet.console.csproj
index 8d3855d6e..07b0d6374 100644
--- a/src/coverlet.console/coverlet.console.csproj
+++ b/src/coverlet.console/coverlet.console.csproj
@@ -1,4 +1,4 @@
-
+
Exe
@@ -6,11 +6,16 @@
coverlet
true
coverlet.console
- tonerdo
+
+
+
+
$(AssemblyTitle)
+ tonerdo
Coverlet is a cross platform code coverage tool for .NET, with support for line, branch and method coverage.
coverage;testing;unit-test;lcov;opencover;quality
https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true
+ coverlet-icon.png
https://github.com/tonerdo/coverlet
MIT
git
@@ -24,4 +29,8 @@
+
+
+
+
diff --git a/src/coverlet.console/version.json b/src/coverlet.console/version.json
index 57c456fb2..119599712 100644
--- a/src/coverlet.console/version.json
+++ b/src/coverlet.console/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "1.6",
+ "version": "1.7",
"publicReleaseRefSpec": [
"^refs/heads/master$"
]
diff --git a/src/coverlet.core/Instrumentation/Instrumenter.cs b/src/coverlet.core/Instrumentation/Instrumenter.cs
index 824d83588..fbedf313d 100644
--- a/src/coverlet.core/Instrumentation/Instrumenter.cs
+++ b/src/coverlet.core/Instrumentation/Instrumenter.cs
@@ -39,6 +39,8 @@ internal class Instrumenter
private MethodReference _customTrackerRecordHitMethod;
private List _excludedSourceFiles;
private List _branchesInCompiledGeneratedClass;
+ private List<(MethodDefinition, int)> _excludedMethods;
+ private List _excludedCompilerGeneratedTypes;
public bool SkipModule { get; set; } = false;
@@ -180,8 +182,19 @@ private void InstrumentModule()
// Instrumenting Interlocked which is used for recording hits would cause an infinite loop.
&& (!_isCoreLibrary || actualType.FullName != "System.Threading.Interlocked")
&& !_instrumentationHelper.IsTypeExcluded(_module, actualType.FullName, _excludeFilters)
- && _instrumentationHelper.IsTypeIncluded(_module, actualType.FullName, _includeFilters))
- InstrumentType(type);
+ && _instrumentationHelper.IsTypeIncluded(_module, actualType.FullName, _includeFilters)
+ )
+ {
+ if (IsSynthesizedMemberToBeExcluded(type))
+ {
+ _excludedCompilerGeneratedTypes ??= new List();
+ _excludedCompilerGeneratedTypes.Add(type.FullName);
+ }
+ else
+ {
+ InstrumentType(type);
+ }
+ }
}
// Fixup the custom tracker class constructor, according to all instrumented types
@@ -335,6 +348,10 @@ private void AddCustomModuleTrackerToModule(ModuleDefinition module)
private void InstrumentType(TypeDefinition type)
{
var methods = type.GetMethods();
+
+ // We keep ordinal index because it's the way used by compiler for generated types/methods to
+ // avoid ambiguity
+ int ordinal = -1;
foreach (var method in methods)
{
MethodDefinition actualMethod = method;
@@ -349,8 +366,21 @@ private void InstrumentType(TypeDefinition type)
customAttributes = customAttributes.Union(prop.CustomAttributes);
}
+ ordinal++;
+ if (IsSynthesizedMemberToBeExcluded(method))
+ {
+ continue;
+ }
+
if (!customAttributes.Any(IsExcludeAttribute))
+ {
InstrumentMethod(method);
+ }
+ else
+ {
+ _excludedMethods ??= new List<(MethodDefinition, int)>();
+ _excludedMethods.Add((method, ordinal));
+ }
}
var ctors = type.GetConstructors();
@@ -604,6 +634,61 @@ private static MethodBody GetMethodBody(MethodDefinition method)
}
}
+ // Check if the member (type or method) is generated by the compiler from a method excluded from code coverage
+ private bool IsSynthesizedMemberToBeExcluded(IMemberDefinition definition)
+ {
+ if (_excludedMethods is null)
+ {
+ return false;
+ }
+
+ TypeDefinition declaringType = definition.DeclaringType;
+
+ // We check all parent type of current one bottom-up
+ while (declaringType != null)
+ {
+
+ // If parent type is excluded return
+ if (_excludedCompilerGeneratedTypes != null &&
+ _excludedCompilerGeneratedTypes.Any(t => t == declaringType.FullName))
+ {
+ return true;
+ }
+
+ // Check methods members and compiler generated types
+ foreach (var excludedMethods in _excludedMethods)
+ {
+ // Exclude this member if declaring type is the same of the excluded method and
+ // the name is synthesized from the name of the excluded method.
+ //
+ if (declaringType.FullName == excludedMethods.Item1.DeclaringType.FullName &&
+ IsSynthesizedNameOf(definition.Name, excludedMethods.Item1.Name, excludedMethods.Item2))
+ {
+ return true;
+ }
+ }
+ declaringType = declaringType.DeclaringType;
+ }
+
+ return false;
+ }
+
+ // Check if the name is synthesized by the compiler
+ // Refer to https://github.com/dotnet/roslyn/blob/master/src/Compilers/CSharp/Portable/Symbols/Synthesized/GeneratedNames.cs
+ // to see how the compiler generate names for lambda, local function, yield or async/await expressions
+ internal bool IsSynthesizedNameOf(string name, string methodName, int methodOrdinal)
+ {
+ return
+ // Lambda method
+ name.IndexOf($"<{methodName}>b__{methodOrdinal}") != -1 ||
+ // Lambda display class
+ name.IndexOf($"<>c__DisplayClass{methodOrdinal}_") != -1 ||
+ // State machine
+ name.IndexOf($"<{methodName}>d__{methodOrdinal}") != -1 ||
+ // Local function
+ (name.IndexOf($"<{methodName}>g__") != -1 && name.IndexOf($"|{methodOrdinal}_") != -1);
+ }
+
///
/// A custom importer created specifically to allow the instrumentation of System.Private.CoreLib by
/// removing the external references to netstandard that are generated when instrumenting a typical
diff --git a/src/coverlet.core/Reporters/CoberturaReporter.cs b/src/coverlet.core/Reporters/CoberturaReporter.cs
index d3c11f956..cebaa569a 100644
--- a/src/coverlet.core/Reporters/CoberturaReporter.cs
+++ b/src/coverlet.core/Reporters/CoberturaReporter.cs
@@ -32,8 +32,8 @@ public string Report(CoverageResult result)
coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds));
XElement sources = new XElement("sources");
- var rootDirs = GetRootDirs(result.Modules, result.UseSourceLink).ToList();
- rootDirs.ForEach(x => sources.Add(new XElement("source", x)));
+ var absolutePaths = GetBasePaths(result.Modules, result.UseSourceLink).ToList();
+ absolutePaths.ForEach(x => sources.Add(new XElement("source", x)));
XElement packages = new XElement("packages");
foreach (var module in result.Modules)
@@ -51,7 +51,7 @@ public string Report(CoverageResult result)
{
XElement @class = new XElement("class");
@class.Add(new XAttribute("name", cls.Key));
- @class.Add(new XAttribute("filename", GetRelativePathFromBase(rootDirs, document.Key, result.UseSourceLink)));
+ @class.Add(new XAttribute("filename", GetRelativePathFromBase(absolutePaths, document.Key, result.UseSourceLink)));
@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)));
@@ -133,28 +133,70 @@ public string Report(CoverageResult result)
return Encoding.UTF8.GetString(stream.ToArray());
}
- private static IEnumerable GetRootDirs(Modules modules, bool useSourceLink)
+ private static IEnumerable GetBasePaths(Modules modules, bool useSourceLink)
{
+ /*
+ Workflow
+
+ Path1 c:\dir1\dir2\file1.cs
+ Path2 c:\dir1\file2.cs
+ Path3 e:\dir1\file2.cs
+
+ 1) Search for root dir
+ c:\ -> c:\dir1\dir2\file1.cs
+ c:\dir1\file2.cs
+ e:\ -> e:\dir1\file2.cs
+
+ 2) Split path on directory separator i.e. for record c:\ ordered ascending by fragment elements
+ Path1 = [c:|dir1|file2.cs]
+ Path2 = [c:|dir1|dir2|file1.cs]
+
+ 3) Find longest shared path comparing indexes
+ Path1[0] = Path2[0], ..., PathY[0] -> add to final fragment list
+ Path1[n] = Path2[n], ..., PathY[n] -> add to final fragment list
+ Path1[n+1] != Path2[n+1], ..., PathY[n+1] -> break, Path1[n] was last shared fragment
+
+ 4) Concat created fragment list
+ */
if (useSourceLink)
{
return new[] { string.Empty };
}
- return modules.Values.SelectMany(k => k.Keys).Select(Directory.GetDirectoryRoot).Distinct();
+ return modules.Values.SelectMany(k => k.Keys).GroupBy(Directory.GetDirectoryRoot).Select(group =>
+ {
+ var splittedPaths = group.Select(absolutePath => absolutePath.Split(Path.DirectorySeparatorChar))
+ .OrderBy(absolutePath => absolutePath.Length).ToList();
+ if (splittedPaths.Count == 1)
+ {
+ return group.Key;
+ }
+
+ var basePathFragments = new List();
+
+ splittedPaths[0].Select((value, index) => (value, index)).ToList().ForEach(fragmentIndexPair =>
+ {
+ if (splittedPaths.All(sp => fragmentIndexPair.value.Equals(sp[fragmentIndexPair.index])))
+ {
+ basePathFragments.Add(fragmentIndexPair.value);
+ }
+ });
+ return string.Concat(string.Join(Path.DirectorySeparatorChar.ToString(), basePathFragments), Path.DirectorySeparatorChar);
+ });
}
- private static string GetRelativePathFromBase(IEnumerable rootPaths, string path, bool useSourceLink)
+ private static string GetRelativePathFromBase(IEnumerable basePaths, string path, bool useSourceLink)
{
if (useSourceLink)
{
return path;
}
- foreach (var root in rootPaths)
+ foreach (var basePath in basePaths)
{
- if (path.StartsWith(root))
+ if (path.StartsWith(basePath))
{
- return path.Substring(root.Length);
+ return path.Substring(basePath.Length);
}
}
diff --git a/src/coverlet.core/coverlet.core.csproj b/src/coverlet.core/coverlet.core.csproj
index 4add60886..357d42e2f 100644
--- a/src/coverlet.core/coverlet.core.csproj
+++ b/src/coverlet.core/coverlet.core.csproj
@@ -3,9 +3,8 @@
Library
netstandard2.0
- 5.2.0
+ 5.3.0
false
- preview
diff --git a/src/coverlet.msbuild.tasks/ReportWriter.cs b/src/coverlet.msbuild.tasks/ReportWriter.cs
index 346822a56..e55963247 100644
--- a/src/coverlet.msbuild.tasks/ReportWriter.cs
+++ b/src/coverlet.msbuild.tasks/ReportWriter.cs
@@ -35,8 +35,8 @@ public void WriteReport()
else if (Path.HasExtension(filename))
{
// filename with extension for instance c:\reportpath\file.ext
- // c:\reportpath\file.ext.reportedextension
- filename = $"{Path.GetFileNameWithoutExtension(filename)}{separatorPoint}{_coverletMultiTargetFrameworksCurrentTFM}{Path.GetExtension(filename)}.{_reporter.Extension}";
+ // we keep user specified name
+ filename = $"{Path.GetFileNameWithoutExtension(filename)}{separatorPoint}{_coverletMultiTargetFrameworksCurrentTFM}{Path.GetExtension(filename)}";
}
else
{
diff --git a/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj b/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj
index 3c3bf2ec6..bcd6a0498 100644
--- a/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj
+++ b/src/coverlet.msbuild.tasks/coverlet.msbuild.tasks.csproj
@@ -4,21 +4,33 @@
Library
netstandard2.0
coverlet.msbuild.tasks
+ true
+ $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs
+ build
+ false
+
+ true
+
+
+
+
coverlet.msbuild
coverlet.msbuild
tonerdo
MIT
http://github.com/tonerdo/coverlet
https://raw.githubusercontent.com/tonerdo/coverlet/master/_assets/coverlet-icon.svg?sanitize=true
+ coverlet-icon.png
false
true
Coverlet is a cross platform code coverage library for .NET, with support for line, branch and method coverage.
coverage testing unit-test lcov opencover quality
git
- true
- $(TargetsForTfmSpecificContentInPackage);PackBuildOutputs
- build
- false
@@ -30,7 +42,7 @@
-
+
@@ -38,6 +50,10 @@
+
+
+
+
diff --git a/src/coverlet.msbuild.tasks/version.json b/src/coverlet.msbuild.tasks/version.json
index 767d4f1e2..8b3745feb 100644
--- a/src/coverlet.msbuild.tasks/version.json
+++ b/src/coverlet.msbuild.tasks/version.json
@@ -1,6 +1,6 @@
{
"$schema": "https://raw.githubusercontent.com/AArnott/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json",
- "version": "2.7",
+ "version": "2.8",
"publicReleaseRefSpec": [
"^refs/heads/master$"
]
diff --git a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs
index 757e00bf1..40d0ec112 100644
--- a/test/coverlet.collector.tests/CoverletSettingsParserTests.cs
+++ b/test/coverlet.collector.tests/CoverletSettingsParserTests.cs
@@ -42,17 +42,33 @@ public void ParseShouldSelectFirstTestModuleFromTestModulesList()
Assert.Equal("module1.dll", coverletSettings.TestModule);
}
- [Fact]
- public void ParseShouldCorrectlyParseConfigurationElement()
+ [Theory]
+ [InlineData("[*]*,[coverlet]*", "[coverlet.*.tests?]*,[coverlet.*.tests.*]*", @"E:\temp,/var/tmp", "module1.cs,module2.cs", "Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute")]
+ [InlineData("[*]*,,[coverlet]*", "[coverlet.*.tests?]*,,[coverlet.*.tests.*]*", @"E:\temp,,/var/tmp", "module1.cs,,module2.cs", "Obsolete,,GeneratedCodeAttribute,,CompilerGeneratedAttribute")]
+ [InlineData("[*]*, ,[coverlet]*", "[coverlet.*.tests?]*, ,[coverlet.*.tests.*]*", @"E:\temp, ,/var/tmp", "module1.cs, ,module2.cs", "Obsolete, ,GeneratedCodeAttribute, ,CompilerGeneratedAttribute")]
+ [InlineData("[*]*,\t,[coverlet]*", "[coverlet.*.tests?]*,\t,[coverlet.*.tests.*]*", "E:\\temp,\t,/var/tmp", "module1.cs,\t,module2.cs", "Obsolete,\t,GeneratedCodeAttribute,\t,CompilerGeneratedAttribute")]
+ [InlineData("[*]*, [coverlet]*", "[coverlet.*.tests?]*, [coverlet.*.tests.*]*", @"E:\temp, /var/tmp", "module1.cs, module2.cs", "Obsolete, GeneratedCodeAttribute, CompilerGeneratedAttribute")]
+ [InlineData("[*]*,\t[coverlet]*", "[coverlet.*.tests?]*,\t[coverlet.*.tests.*]*", "E:\\temp,\t/var/tmp", "module1.cs,\tmodule2.cs", "Obsolete,\tGeneratedCodeAttribute,\tCompilerGeneratedAttribute")]
+ [InlineData("[*]*, \t[coverlet]*", "[coverlet.*.tests?]*, \t[coverlet.*.tests.*]*", "E:\\temp, \t/var/tmp", "module1.cs, \tmodule2.cs", "Obsolete, \tGeneratedCodeAttribute, \tCompilerGeneratedAttribute")]
+ [InlineData("[*]*,\r\n[coverlet]*", "[coverlet.*.tests?]*,\r\n[coverlet.*.tests.*]*", "E:\\temp,\r\n/var/tmp", "module1.cs,\r\nmodule2.cs", "Obsolete,\r\nGeneratedCodeAttribute,\r\nCompilerGeneratedAttribute")]
+ [InlineData("[*]*, \r\n [coverlet]*", "[coverlet.*.tests?]*, \r\n [coverlet.*.tests.*]*", "E:\\temp, \r\n /var/tmp", "module1.cs, \r\n module2.cs", "Obsolete, \r\n GeneratedCodeAttribute, \r\n CompilerGeneratedAttribute")]
+ [InlineData("[*]*,\t\r\n\t[coverlet]*", "[coverlet.*.tests?]*,\t\r\n\t[coverlet.*.tests.*]*", "E:\\temp,\t\r\n\t/var/tmp", "module1.cs,\t\r\n\tmodule2.cs", "Obsolete,\t\r\n\tGeneratedCodeAttribute,\t\r\n\tCompilerGeneratedAttribute")]
+ [InlineData("[*]*, \t \r\n \t [coverlet]*", "[coverlet.*.tests?]*, \t \r\n \t [coverlet.*.tests.*]*", "E:\\temp, \t \r\n \t /var/tmp", "module1.cs, \t \r\n \t module2.cs", "Obsolete, \t \r\n \t GeneratedCodeAttribute, \t \r\n \t CompilerGeneratedAttribute")]
+ [InlineData(" [*]* , [coverlet]* ", " [coverlet.*.tests?]* , [coverlet.*.tests.*]* ", " E:\\temp , /var/tmp ", " module1.cs , module2.cs ", " Obsolete , GeneratedCodeAttribute , CompilerGeneratedAttribute ")]
+ public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters,
+ string excludeFilters,
+ string includeDirectories,
+ string excludeSourceFiles,
+ string excludeAttributes)
{
var testModules = new List { "abc.dll" };
var doc = new XmlDocument();
var configElement = doc.CreateElement("Configuration");
- this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName, "[*]*");
- this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName, "[coverlet.*.tests?]*");
- this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName, @"E:\temp");
- this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName, "module1.cs,module2.cs");
- this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName, "Obsolete,GeneratedCodeAttribute,CompilerGeneratedAttribute");
+ this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName, includeFilters);
+ this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName, excludeFilters);
+ this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName, includeDirectories);
+ this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName, excludeSourceFiles);
+ this.CreateCoverletNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName, excludeAttributes);
this.CreateCoverletNodes(doc, configElement, CoverletConstants.MergeWithElementName, "/path/to/result.json");
this.CreateCoverletNodes(doc, configElement, CoverletConstants.UseSourceLinkElementName, "false");
this.CreateCoverletNodes(doc, configElement, CoverletConstants.SingleHitElementName, "true");
@@ -62,24 +78,76 @@ public void ParseShouldCorrectlyParseConfigurationElement()
Assert.Equal("abc.dll", coverletSettings.TestModule);
Assert.Equal("[*]*", coverletSettings.IncludeFilters[0]);
+ Assert.Equal("[coverlet]*", coverletSettings.IncludeFilters[1]);
Assert.Equal(@"E:\temp", coverletSettings.IncludeDirectories[0]);
+ Assert.Equal("/var/tmp", coverletSettings.IncludeDirectories[1]);
Assert.Equal("module1.cs", coverletSettings.ExcludeSourceFiles[0]);
Assert.Equal("module2.cs", coverletSettings.ExcludeSourceFiles[1]);
Assert.Equal("Obsolete", coverletSettings.ExcludeAttributes[0]);
Assert.Equal("GeneratedCodeAttribute", coverletSettings.ExcludeAttributes[1]);
+ Assert.Equal("CompilerGeneratedAttribute", coverletSettings.ExcludeAttributes[2]);
Assert.Equal("/path/to/result.json", coverletSettings.MergeWith);
Assert.Equal("[coverlet.*]*", coverletSettings.ExcludeFilters[0]);
+ Assert.Equal("[coverlet.*.tests?]*", coverletSettings.ExcludeFilters[1]);
+ Assert.Equal("[coverlet.*.tests.*]*", coverletSettings.ExcludeFilters[2]);
+
Assert.False(coverletSettings.UseSourceLink);
Assert.True(coverletSettings.SingleHit);
Assert.True(coverletSettings.IncludeTestAssembly);
}
+ [Fact]
+ public void ParseShouldCorrectlyParseConfigurationElementWithNullInnerText()
+ {
+ var testModules = new List { "abc.dll" };
+ var doc = new XmlDocument();
+ var configElement = doc.CreateElement("Configuration");
+ this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.IncludeFiltersElementName);
+ this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeFiltersElementName);
+ this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.IncludeDirectoriesElementName);
+ this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName);
+ this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName);
+
+ CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules);
+
+ Assert.Equal("abc.dll", coverletSettings.TestModule);
+ Assert.Empty(coverletSettings.IncludeFilters);
+ Assert.Empty(coverletSettings.IncludeDirectories);
+ Assert.Empty(coverletSettings.ExcludeSourceFiles);
+ Assert.Empty(coverletSettings.ExcludeAttributes);
+ Assert.Single(coverletSettings.ExcludeFilters, "[coverlet.*]*");
+ }
+
+ [Fact]
+ public void ParseShouldCorrectlyParseConfigurationElementWithNullElements()
+ {
+ var testModules = new List { "abc.dll" };
+ var doc = new XmlDocument();
+ var configElement = doc.CreateElement("Configuration");
+
+ CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules);
+
+ Assert.Equal("abc.dll", coverletSettings.TestModule);
+ Assert.Null(coverletSettings.IncludeFilters);
+ Assert.Null(coverletSettings.IncludeDirectories);
+ Assert.Null(coverletSettings.ExcludeSourceFiles);
+ Assert.Null(coverletSettings.ExcludeAttributes);
+ Assert.Single(coverletSettings.ExcludeFilters, "[coverlet.*]*");
+ }
+
[Theory]
[InlineData(" , json", 1, new[] { "json" })]
[InlineData(" , json, ", 1, new[] { "json" })]
[InlineData("json,cobertura", 2, new[] { "json", "cobertura" })]
+ [InlineData("json,\r\ncobertura", 2, new[] { "json", "cobertura" })]
+ [InlineData("json, \r\n cobertura", 2, new[] { "json", "cobertura" })]
+ [InlineData("json,\tcobertura", 2, new[] { "json", "cobertura" })]
+ [InlineData("json, \t cobertura", 2, new[] { "json", "cobertura" })]
+ [InlineData("json,\t\r\n\tcobertura", 2, new[] { "json", "cobertura" })]
+ [InlineData("json, \t \r\n \tcobertura", 2, new[] { "json", "cobertura" })]
[InlineData(" , json,, cobertura ", 2, new[] { "json", "cobertura" })]
[InlineData(" , json, , cobertura ", 2, new[] { "json", "cobertura" })]
+ [InlineData(",json,\t,\r\n,cobertura", 2, new[] { "json", "cobertura" })]
public void ParseShouldCorrectlyParseMultipleFormats(string formats, int formatsCount, string[] expectedReportFormats)
{
var testModules = new List { "abc.dll" };
@@ -115,5 +183,12 @@ private void CreateCoverletNodes(XmlDocument doc, XmlElement configElement, stri
node.InnerText = nodeValue;
configElement.AppendChild(node);
}
+
+ private void CreateCoverleteNullInnerTextNodes(XmlDocument doc, XmlElement configElement, string nodeSetting)
+ {
+ var node = doc.CreateNode("element", nodeSetting, string.Empty);
+ node.InnerText = null;
+ configElement.AppendChild(node);
+ }
}
}
diff --git a/test/coverlet.collector.tests/coverlet.collector.tests.csproj b/test/coverlet.collector.tests/coverlet.collector.tests.csproj
index b8ff66c36..0e2c3c697 100644
--- a/test/coverlet.collector.tests/coverlet.collector.tests.csproj
+++ b/test/coverlet.collector.tests/coverlet.collector.tests.csproj
@@ -2,9 +2,8 @@
- netcoreapp2.2
+ netcoreapp3.1
false
- preview
diff --git a/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj b/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj
index 893340c17..1c30298df 100644
--- a/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj
+++ b/test/coverlet.core.performancetest/coverlet.core.performancetest.csproj
@@ -2,7 +2,7 @@
- netcoreapp2.0
+ netcoreapp3.1
false
diff --git a/test/coverlet.core.tests/Coverage/CoverageTests.cs b/test/coverlet.core.tests/Coverage/CoverageTests.cs
index 785a5c322..7d5dc0bfe 100644
--- a/test/coverlet.core.tests/Coverage/CoverageTests.cs
+++ b/test/coverlet.core.tests/Coverage/CoverageTests.cs
@@ -259,5 +259,107 @@ public void Lambda_Issue343()
File.Delete(path);
}
}
+
+
+ [Fact]
+ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes()
+ {
+ string path = Path.GetTempFileName();
+ try
+ {
+ RemoteExecutor.Invoke(async pathSerialize =>
+ {
+ CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance =>
+ {
+ ((Task)instance.Test("test")).ConfigureAwait(false).GetAwaiter().GetResult();
+ return Task.CompletedTask;
+ }, pathSerialize);
+
+ return 0;
+
+ }, path).Dispose();
+
+ CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path);
+
+ var document = result.Document("Instrumentation.ExcludeFromCoverage.cs");
+
+ // Invoking method "Test" of class "MethodsWithExcludeFromCodeCoverageAttr" we expect to cover 100% lines for MethodsWithExcludeFromCodeCoverageAttr
+ Assert.DoesNotContain(document.Lines, l =>
+ (l.Value.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr" ||
+ // Compiler generated
+ l.Value.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/")) &&
+ l.Value.Hits == 0);
+ // and 0% for MethodsWithExcludeFromCodeCoverageAttr2
+ Assert.DoesNotContain(document.Lines, l =>
+ (l.Value.Class == "Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2" ||
+ // Compiler generated
+ l.Value.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/")) &&
+ l.Value.Hits == 1);
+ }
+ finally
+ {
+ File.Delete(path);
+ }
+ }
+
+ [Fact]
+ public void ExcludeFromCodeCoverage_CompilerGeneratedMethodsAndTypes_NestedMembers()
+ {
+ string path = Path.GetTempFileName();
+ try
+ {
+ RemoteExecutor.Invoke(async pathSerialize =>
+ {
+ CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance =>
+ {
+ instance.Test();
+ return Task.CompletedTask;
+ }, pathSerialize);
+
+ return 0;
+
+ }, path).Dispose();
+
+ CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path);
+
+ result.Document("Instrumentation.ExcludeFromCoverage.NestedStateMachines.cs")
+ .AssertLinesCovered(BuildConfiguration.Debug, (14, 1), (15, 1), (16, 1))
+ .AssertNonInstrumentedLines(BuildConfiguration.Debug, 9, 11);
+ }
+ finally
+ {
+ File.Delete(path);
+ }
+ }
+
+ [Fact]
+ public void ExcludeFromCodeCoverageCompilerGeneratedMethodsAndTypes_Issue670()
+ {
+ string path = Path.GetTempFileName();
+ try
+ {
+ RemoteExecutor.Invoke(async pathSerialize =>
+ {
+ CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance =>
+ {
+ instance.Test("test");
+ return Task.CompletedTask;
+ }, pathSerialize);
+
+ return 0;
+
+ }, path).Dispose();
+
+ CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path);
+
+ result.Document("Instrumentation.ExcludeFromCoverage.Issue670.cs")
+ .AssertLinesCovered(BuildConfiguration.Debug, (8, 1), (9, 1), (10, 1), (11, 1))
+ .AssertNonInstrumentedLines(BuildConfiguration.Debug, 15, 53);
+ }
+ finally
+ {
+ File.Delete(path);
+ }
+ }
}
}
\ No newline at end of file
diff --git a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
index 35b4ff1d5..3034c07d6 100644
--- a/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
+++ b/test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
@@ -219,6 +219,30 @@ public static Document AssertLinesCovered(this Document document, BuildConfigura
return document;
}
+ public static Document AssertNonInstrumentedLines(this Document document, BuildConfiguration configuration, int from, int to)
+ {
+ if (document is null)
+ {
+ throw new ArgumentNullException(nameof(document));
+ }
+
+ BuildConfiguration buildConfiguration = GetAssemblyBuildConfiguration();
+
+ if ((buildConfiguration & configuration) != buildConfiguration)
+ {
+ return document;
+ }
+
+ int[] lineRange = Enumerable.Range(from, to - from + 1).ToArray();
+
+ if (document.Lines.Select(l => l.Value.Number).Intersect(lineRange).Count() > 0)
+ {
+ throw new XunitException($"Unexpected instrumented lines, '{string.Join(',', lineRange)}'");
+ }
+
+ return document;
+ }
+
private static BuildConfiguration GetAssemblyBuildConfiguration()
{
var configurationAttribute = Assembly.GetExecutingAssembly().GetCustomAttribute();
diff --git a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
index 3772040d2..e311bfce3 100644
--- a/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
+++ b/test/coverlet.core.tests/Helpers/InstrumentationHelperTests.cs
@@ -143,7 +143,7 @@ public void TestIsTypeExcludedWithoutFilter()
[Fact]
public void TestIsTypeExcludedNamespace()
{
- var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "Namespace.Namespace.Type", new string[]{ "[Module]Namespace.Namespace.*" });
+ var result = _instrumentationHelper.IsTypeExcluded("Module.dll", "Namespace.Namespace.Type", new string[] { "[Module]Namespace.Namespace.*" });
Assert.True(result);
result = _instrumentationHelper.IsTypeExcluded("Module.dll", "Namespace.Namespace.TypeB", new string[] { "[Module]Namespace.Namespace.*" });
@@ -206,23 +206,24 @@ public void TestIncludeDirectories()
{
string module = typeof(InstrumentationHelperTests).Assembly.Location;
- var currentDirModules = _instrumentationHelper.GetCoverableModules(module,
- new[] { Environment.CurrentDirectory }, false)
- .Where(m => !m.StartsWith("testgen_")).ToArray();
+ DirectoryInfo newDir = Directory.CreateDirectory("TestIncludeDirectories");
+ DirectoryInfo newDir2 = Directory.CreateDirectory("TestIncludeDirectories2");
+ File.Copy(module, Path.Combine(newDir.FullName, Path.GetFileName(module)));
+ module = Path.Combine(newDir.FullName, Path.GetFileName(module));
+ File.Copy("coverlet.msbuild.tasks.dll", Path.Combine(newDir.FullName, "coverlet.msbuild.tasks.dll"));
+ File.Copy("coverlet.core.dll", Path.Combine(newDir2.FullName, "coverlet.core.dll"));
- var parentDirWildcardModules = _instrumentationHelper.GetCoverableModules(module,
- new[] { Path.Combine(Directory.GetParent(Environment.CurrentDirectory).FullName, "*") }, false)
- .Where(m => !m.StartsWith("testgen_")).ToArray();
+ var currentDirModules = _instrumentationHelper.GetCoverableModules(module, Array.Empty(), false);
+ Assert.Single(currentDirModules);
+ Assert.Equal("coverlet.msbuild.tasks.dll", Path.GetFileName(currentDirModules[0]));
- // There are at least as many modules found when searching the parent directory's subdirectories
- Assert.True(parentDirWildcardModules.Length >= currentDirModules.Length);
+ var moreThanOneDirectory = _instrumentationHelper.GetCoverableModules(module, new string[] { newDir2.FullName }, false);
+ Assert.Equal(2, moreThanOneDirectory.Length);
+ Assert.Equal("coverlet.msbuild.tasks.dll", Path.GetFileName(moreThanOneDirectory[0]));
+ Assert.Equal("coverlet.core.dll", Path.GetFileName(moreThanOneDirectory[1]));
- var relativePathModules = _instrumentationHelper.GetCoverableModules(module,
- new[] { "." }, false)
- .Where(m => !m.StartsWith("testgen_")).ToArray();
-
- // Same number of modules found when using a relative path
- Assert.Equal(currentDirModules.Length, relativePathModules.Length);
+ newDir.Delete(true);
+ newDir2.Delete(true);
}
public static IEnumerable