Skip to content

Add skipautoprops feature #912

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
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Documentation/GlobalTool.md
Original file line number Diff line number Diff line change
@@ -36,6 +36,7 @@ Options:
--single-hit Specifies whether to limit code coverage hit reporting to a single hit for each location.
--merge-with Path to existing coverage result to merge.
--use-source-link Specifies whether to use SourceLink URIs in place of file system paths.
--skipautoprops Neither track nor record auto-implemented properties.
```
NB. For a [multiple value] options you have to specify values multiple times i.e.
5 changes: 5 additions & 0 deletions Documentation/MSBuildIntegration.md
Original file line number Diff line number Diff line change
@@ -157,6 +157,11 @@ Both `Exclude` and `Include` properties can be used together but `Exclude` takes

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

### Skip auto-implemented properties

Neither track nor record auto-implemented properties.
Syntax: `/p:SkipAutoProps=true`

### Note for Powershell / VSTS users
To exclude or include multiple assemblies when using Powershell scripts or creating a .yaml file for a VSTS build ```%2c``` should be used as a separator. Msbuild will translate this symbol to ```,```.

2 changes: 2 additions & 0 deletions Documentation/VSTestIntegration.md
Original file line number Diff line number Diff line change
@@ -79,6 +79,7 @@ These are a list of options that are supported by coverlet. These can be specifi
|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. |
|SkipAutoProps | Neither track nor record auto-implemented properties. |

How to specify these options via runsettings?
```
@@ -97,6 +98,7 @@ How to specify these options via runsettings?
<SingleHit>false</SingleHit>
<UseSourceLink>true</UseSourceLink>
<IncludeTestAssembly>true</IncludeTestAssembly>
<SkipAutoProps>true</SkipAutoProps>
</Configuration>
</DataCollector>
</DataCollectors>
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -164,6 +164,10 @@ to clarify expected behavior in our community.

For more information, see the [.NET Foundation Code of Conduct](https://dotnetfoundation.org/code-of-conduct).

## Credits

Part of the code is based on work done by OpenCover team https://github.com/OpenCover

## License

This project is licensed under the MIT license. See the [LICENSE](LICENSE) file for more info.
3 changes: 2 additions & 1 deletion src/coverlet.collector/DataCollection/CoverageWrapper.cs
Original file line number Diff line number Diff line change
@@ -27,7 +27,8 @@ public Coverage CreateCoverage(CoverletSettings settings, ILogger coverletLogger
IncludeTestAssembly = settings.IncludeTestAssembly,
SingleHit = settings.SingleHit,
MergeWith = settings.MergeWith,
UseSourceLink = settings.UseSourceLink
UseSourceLink = settings.UseSourceLink,
SkipAutoProps = settings.SkipAutoProps
};

return new Coverage(
26 changes: 16 additions & 10 deletions src/coverlet.collector/DataCollection/CoverletSettings.cs
Original file line number Diff line number Diff line change
@@ -63,20 +63,26 @@ internal class CoverletSettings
/// </summary>
public bool IncludeTestAssembly { get; set; }

/// <summary>
/// Neither track nor record auto-implemented properties.
/// </summary>
public bool SkipAutoProps { get; set; }

public override string ToString()
{
var builder = new StringBuilder();

builder.AppendFormat("TestModule: '{0}', ", this.TestModule);
builder.AppendFormat("IncludeFilters: '{0}', ", string.Join(",", this.IncludeFilters ?? Enumerable.Empty<string>()));
builder.AppendFormat("IncludeDirectories: '{0}', ", string.Join(",", this.IncludeDirectories ?? Enumerable.Empty<string>()));
builder.AppendFormat("ExcludeFilters: '{0}', ", string.Join(",", this.ExcludeFilters ?? Enumerable.Empty<string>()));
builder.AppendFormat("ExcludeSourceFiles: '{0}', ", string.Join(",", this.ExcludeSourceFiles ?? Enumerable.Empty<string>()));
builder.AppendFormat("ExcludeAttributes: '{0}', ", string.Join(",", this.ExcludeAttributes ?? Enumerable.Empty<string>()));
builder.AppendFormat("MergeWith: '{0}', ", this.MergeWith);
builder.AppendFormat("UseSourceLink: '{0}'", this.UseSourceLink);
builder.AppendFormat("SingleHit: '{0}'", this.SingleHit);
builder.AppendFormat("IncludeTestAssembly: '{0}'", this.IncludeTestAssembly);
builder.AppendFormat("TestModule: '{0}', ", TestModule);
builder.AppendFormat("IncludeFilters: '{0}', ", string.Join(",", IncludeFilters ?? Enumerable.Empty<string>()));
builder.AppendFormat("IncludeDirectories: '{0}', ", string.Join(",", IncludeDirectories ?? Enumerable.Empty<string>()));
builder.AppendFormat("ExcludeFilters: '{0}', ", string.Join(",", ExcludeFilters ?? Enumerable.Empty<string>()));
builder.AppendFormat("ExcludeSourceFiles: '{0}', ", string.Join(",", ExcludeSourceFiles ?? Enumerable.Empty<string>()));
builder.AppendFormat("ExcludeAttributes: '{0}', ", string.Join(",", ExcludeAttributes ?? Enumerable.Empty<string>()));
builder.AppendFormat("MergeWith: '{0}', ", MergeWith);
builder.AppendFormat("UseSourceLink: '{0}'", UseSourceLink);
builder.AppendFormat("SingleHit: '{0}'", SingleHit);
builder.AppendFormat("IncludeTestAssembly: '{0}'", IncludeTestAssembly);
builder.AppendFormat("SkipAutoProps: '{0}'", SkipAutoProps);

return builder.ToString();
}
35 changes: 24 additions & 11 deletions src/coverlet.collector/DataCollection/CoverletSettingsParser.cs
Original file line number Diff line number Diff line change
@@ -29,23 +29,24 @@ public CoverletSettings Parse(XmlElement configurationElement, IEnumerable<strin
{
var coverletSettings = new CoverletSettings
{
TestModule = this.ParseTestModule(testModules)
TestModule = ParseTestModule(testModules)
};

if (configurationElement != null)
{
coverletSettings.IncludeFilters = this.ParseIncludeFilters(configurationElement);
coverletSettings.IncludeDirectories = this.ParseIncludeDirectories(configurationElement);
coverletSettings.ExcludeAttributes = this.ParseExcludeAttributes(configurationElement);
coverletSettings.ExcludeSourceFiles = this.ParseExcludeSourceFiles(configurationElement);
coverletSettings.MergeWith = this.ParseMergeWith(configurationElement);
coverletSettings.UseSourceLink = this.ParseUseSourceLink(configurationElement);
coverletSettings.SingleHit = this.ParseSingleHit(configurationElement);
coverletSettings.IncludeTestAssembly = this.ParseIncludeTestAssembly(configurationElement);
coverletSettings.IncludeFilters = ParseIncludeFilters(configurationElement);
coverletSettings.IncludeDirectories = ParseIncludeDirectories(configurationElement);
coverletSettings.ExcludeAttributes = ParseExcludeAttributes(configurationElement);
coverletSettings.ExcludeSourceFiles = ParseExcludeSourceFiles(configurationElement);
coverletSettings.MergeWith = ParseMergeWith(configurationElement);
coverletSettings.UseSourceLink = ParseUseSourceLink(configurationElement);
coverletSettings.SingleHit = ParseSingleHit(configurationElement);
coverletSettings.IncludeTestAssembly = ParseIncludeTestAssembly(configurationElement);
coverletSettings.SkipAutoProps = ParseSkipAutoProps(configurationElement);
}

coverletSettings.ReportFormats = this.ParseReportFormats(configurationElement);
coverletSettings.ExcludeFilters = this.ParseExcludeFilters(configurationElement);
coverletSettings.ReportFormats = ParseReportFormats(configurationElement);
coverletSettings.ExcludeFilters = ParseExcludeFilters(configurationElement);

if (_eqtTrace.IsVerboseEnabled)
{
@@ -205,6 +206,18 @@ private bool ParseIncludeTestAssembly(XmlElement configurationElement)
return includeTestAssembly;
}

/// <summary>
/// Parse skipautoprops flag
/// </summary>
/// <param name="configurationElement">Configuration element</param>
/// <returns>Include Test Assembly Flag</returns>
private bool ParseSkipAutoProps(XmlElement configurationElement)
{
XmlElement skipAutoPropsElement = configurationElement[CoverletConstants.SkipAutoProps];
bool.TryParse(skipAutoPropsElement?.InnerText, out bool skipAutoProps);
return skipAutoProps;
}

/// <summary>
/// Splits a comma separated elements into an array
/// </summary>
1 change: 1 addition & 0 deletions src/coverlet.collector/Utilities/CoverletConstants.cs
Original file line number Diff line number Diff line change
@@ -20,5 +20,6 @@ internal static class CoverletConstants
public const string ReportFormatElementName = "Format";
public const string DefaultExcludeFilter = "[coverlet.*]*";
public const string InProcDataCollectorName = "CoverletInProcDataCollector";
public const string SkipAutoProps = "SkipAutoProps";
}
}
6 changes: 4 additions & 2 deletions src/coverlet.console/Program.cs
Original file line number Diff line number Diff line change
@@ -60,6 +60,7 @@ static int Main(string[] args)
CommandOption excludeAttributes = app.Option("--exclude-by-attribute", "Attributes to exclude from code coverage.", CommandOptionType.MultipleValue);
CommandOption includeTestAssembly = app.Option("--include-test-assembly", "Specifies whether to report code coverage of the test assembly.", CommandOptionType.NoValue);
CommandOption singleHit = app.Option("--single-hit", "Specifies whether to limit code coverage hit reporting to a single hit for each location", CommandOptionType.NoValue);
CommandOption skipAutoProp = app.Option("--skipautoprops", "Neither track nor record auto-implemented properties.", CommandOptionType.NoValue);
CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue);
CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue);

@@ -87,7 +88,8 @@ static int Main(string[] args)
IncludeTestAssembly = includeTestAssembly.HasValue(),
SingleHit = singleHit.HasValue(),
MergeWith = mergeWith.Value(),
UseSourceLink = useSourceLink.HasValue()
UseSourceLink = useSourceLink.HasValue(),
SkipAutoProps = skipAutoProp.HasValue()
};

Coverage coverage = new Coverage(module.Value,
@@ -97,7 +99,7 @@ static int Main(string[] args)
fileSystem,
serviceProvider.GetRequiredService<ISourceRootTranslator>(),
serviceProvider.GetRequiredService<ICecilSymbolHelper>());
coverage.PrepareModules();
coverage.PrepareModules();

Process process = new Process();
process.StartInfo.FileName = target.Value();
17 changes: 16 additions & 1 deletion src/coverlet.core/Coverage.cs
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ internal class CoverageParameters
public bool SingleHit { get; set; }
public string MergeWith { get; set; }
public bool UseSourceLink { get; set; }
public bool SkipAutoProps { get; set; }
}

internal class Coverage
@@ -38,6 +39,7 @@ internal class Coverage
private bool _singleHit;
private string _mergeWith;
private bool _useSourceLink;
private bool _skipAutoProps;
private ILogger _logger;
private IInstrumentationHelper _instrumentationHelper;
private IFileSystem _fileSystem;
@@ -73,6 +75,7 @@ public Coverage(string module,
_fileSystem = fileSystem;
_sourceRootTranslator = sourceRootTranslator;
_cecilSymbolHelper = cecilSymbolHelper;
_skipAutoProps = parameters.SkipAutoProps;

_identifier = Guid.NewGuid().ToString();
_results = new List<InstrumenterResult>();
@@ -115,7 +118,19 @@ public CoveragePrepareResult PrepareModules()
continue;
}

var instrumenter = new Instrumenter(module, _identifier, _excludeFilters, _includeFilters, _excludedSourceFiles, _excludeAttributes, _singleHit, _logger, _instrumentationHelper, _fileSystem, _sourceRootTranslator, _cecilSymbolHelper);
var instrumenter = new Instrumenter(module,
_identifier,
_excludeFilters,
_includeFilters,
_excludedSourceFiles,
_excludeAttributes,
_singleHit,
_skipAutoProps,
_logger,
_instrumentationHelper,
_fileSystem,
_sourceRootTranslator,
_cecilSymbolHelper);

if (instrumenter.CanInstrument())
{
8 changes: 8 additions & 0 deletions src/coverlet.core/Instrumentation/Instrumenter.cs
Original file line number Diff line number Diff line change
@@ -25,6 +25,7 @@ internal class Instrumenter
private readonly ExcludedFilesHelper _excludedFilesHelper;
private readonly string[] _excludedAttributes;
private readonly bool _singleHit;
private readonly bool _skipAutoProps;
private readonly bool _isCoreLibrary;
private readonly ILogger _logger;
private readonly IInstrumentationHelper _instrumentationHelper;
@@ -55,6 +56,7 @@ public Instrumenter(
string[] excludedFiles,
string[] excludedAttributes,
bool singleHit,
bool skipAutoProps,
ILogger logger,
IInstrumentationHelper instrumentationHelper,
IFileSystem fileSystem,
@@ -84,6 +86,7 @@ public Instrumenter(
_fileSystem = fileSystem;
_sourceRootTranslator = sourceRootTranslator;
_cecilSymbolHelper = cecilSymbolHelper;
_skipAutoProps = skipAutoProps;
}

public bool CanInstrument()
@@ -432,6 +435,11 @@ private void InstrumentType(TypeDefinition type)

if (actualMethod.IsGetter || actualMethod.IsSetter)
{
if (_skipAutoProps && actualMethod.CustomAttributes.Any(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName))
{
continue;
}

PropertyDefinition prop = type.Properties.FirstOrDefault(p => (p.GetMethod ?? p.SetMethod).FullName.Equals(actualMethod.FullName));
if (prop?.HasCustomAttributes == true)
customAttributes = customAttributes.Union(prop.CustomAttributes);
71 changes: 18 additions & 53 deletions src/coverlet.msbuild.tasks/CoverageResultTask.cs
Original file line number Diff line number Diff line change
@@ -14,62 +14,27 @@ namespace Coverlet.MSbuild.Tasks
{
public class CoverageResultTask : BaseTask
{
private string _output;
private string _format;
private double _threshold;
private string _thresholdType;
private string _thresholdStat;
private string _coverletMultiTargetFrameworksCurrentTFM;
private ITaskItem _instrumenterState;
private MSBuildLogger _logger;

[Required]
public string Output
{
get { return _output; }
set { _output = value; }
}
public string Output { get; set; }
Copy link
Collaborator

Choose a reason for hiding this comment

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

One reason I use explicit backing fields is to prevent the overhead of frequent method calls. It's probably bad for performance

Copy link
Collaborator Author

@MarcoRossignoli MarcoRossignoli Aug 3, 2020

Choose a reason for hiding this comment

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

I think that autoprops are inlined by jitter, take a look here https://sharplab.io/#v2:C4LghgzgtgPgAgJgIwFgBQcDMACR2DC2A3utmbjvgBQCUp5Ja5z2ACgE4D2ADhNgLzYkABgDc9MgF8JFbAEsAdsDZdexAOYBTYKIjbR0tIfTosuBNgCy6Rsxlm4AFmwAVTRGC0ZtlmUIBjAWwFTQB3AlpxJl9cJABOKn8AOg4eCBoo5kk7I3QgA=

image

Also I agree in case of hot loops, but here this properties are accessed only one time on msbuild task usage.
What do you think?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@jkotas, sorry to bother you, can you confirm that autoprops are inlined by jitter?

Copy link
Collaborator

@tonerdo tonerdo Aug 3, 2020

Choose a reason for hiding this comment

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

I was referring more to the general case because we're targeting .NET standard. Inlining of auto props is an implementation detail of the JIT, at least I don't recall it being in the CLI spec. Do we have any plans to support other runtimes beyond .NET Core?

Copy link
Collaborator Author

@MarcoRossignoli MarcoRossignoli Aug 3, 2020

Choose a reason for hiding this comment

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

Do we have any plans to support other runtimes beyond .NET Core?

We support .NET Framework at the moment(not yet on collectors but I'm waiting some answer from vstest team). In this case(inlining apart) I would prefer clean syntax over perf...I mean this code is called only one time for asm, we spend a lot of time on IL instrumentation(I think mangnitude orders), btw I won't oppose if you prefer custom back field, let me know!It was instinctive from my side 😄

Copy link

Choose a reason for hiding this comment

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

Any runtime where performance matters will inline auto-properties just fine. It is certainly the case for both .NET Framework and .NET Core.

If you wanted to optimize for hypothetical runtimes that are missing basic optimizations, your code would look very unnatural. You would want to e.g. prefer public fields over properties, minimize number of methods and making methods as big as possible, etc. It does not sounds like a good idea.


[Required]
public string OutputFormat
{
get { return _format; }
set { _format = value; }
}
public string OutputFormat { get; set; }

[Required]
public double Threshold
{
get { return _threshold; }
set { _threshold = value; }
}
public double Threshold { get; set; }

[Required]
public string ThresholdType
{
get { return _thresholdType; }
set { _thresholdType = value; }
}
public string ThresholdType { get; set; }

[Required]
public string ThresholdStat
{
get { return _thresholdStat; }
set { _thresholdStat = value; }
}
public string ThresholdStat { get; set; }

[Required]
public ITaskItem InstrumenterState
{
get { return _instrumenterState; }
set { _instrumenterState = value; }
}
public ITaskItem InstrumenterState { get; set; }

public string CoverletMultiTargetFrameworksCurrentTFM
{
get { return _coverletMultiTargetFrameworksCurrentTFM; }
set { _coverletMultiTargetFrameworksCurrentTFM = value; }
}
public string CoverletMultiTargetFrameworksCurrentTFM { get; set; }

public CoverageResultTask()
{
@@ -111,7 +76,7 @@ public override bool Execute()

CoverageResult result = coverage.GetCoverageResult();

var directory = Path.GetDirectoryName(_output);
var directory = Path.GetDirectoryName(Output);
if (directory == string.Empty)
{
directory = Directory.GetCurrentDirectory();
@@ -121,7 +86,7 @@ public override bool Execute()
Directory.CreateDirectory(directory);
}

var formats = _format.Split(',');
var formats = OutputFormat.Split(',');
foreach (var format in formats)
{
var reporter = new ReporterFactory(format).CreateReporter();
@@ -138,9 +103,9 @@ public override bool Execute()
}
else
{
ReportWriter writer = new ReportWriter(_coverletMultiTargetFrameworksCurrentTFM,
ReportWriter writer = new ReportWriter(CoverletMultiTargetFrameworksCurrentTFM,
directory,
_output,
Output,
reporter,
fileSystem,
ServiceProvider.GetService<IConsole>(),
@@ -152,7 +117,7 @@ public override bool Execute()
var thresholdTypeFlags = ThresholdTypeFlags.None;
var thresholdStat = ThresholdStatistic.Minimum;

foreach (var thresholdType in _thresholdType.Split(',').Select(t => t.Trim()))
foreach (var thresholdType in ThresholdType.Split(',').Select(t => t.Trim()))
{
if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase))
{
@@ -168,11 +133,11 @@ public override bool Execute()
}
}

if (_thresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase))
if (ThresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase))
{
thresholdStat = ThresholdStatistic.Average;
}
else if (_thresholdStat.Equals("total", StringComparison.OrdinalIgnoreCase))
else if (ThresholdStat.Equals("total", StringComparison.OrdinalIgnoreCase))
{
thresholdStat = ThresholdStatistic.Total;
}
@@ -214,23 +179,23 @@ public override bool Execute()

Console.WriteLine(coverageTable.ToStringAlternative());

thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, _threshold, thresholdTypeFlags, thresholdStat);
thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, Threshold, thresholdTypeFlags, thresholdStat);
if (thresholdTypeFlags != ThresholdTypeFlags.None)
{
var exceptionMessageBuilder = new StringBuilder();
if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None)
{
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {_threshold}");
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {Threshold}");
}

if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None)
{
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {_threshold}");
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {Threshold}");
}

if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None)
{
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {_threshold}");
exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {Threshold}");
}

throw new Exception(exceptionMessageBuilder.ToString());
102 changes: 25 additions & 77 deletions src/coverlet.msbuild.tasks/InstrumentationTask.cs
Original file line number Diff line number Diff line change
@@ -15,86 +15,33 @@ namespace Coverlet.MSbuild.Tasks
{
public class InstrumentationTask : BaseTask
{
private string _path;
private string _include;
private string _includeDirectory;
private string _exclude;
private string _excludeByFile;
private string _excludeByAttribute;
private bool _includeTestAssembly;
private bool _singleHit;
private string _mergeWith;
private bool _useSourceLink;
private ITaskItem _instrumenterState;
private readonly MSBuildLogger _logger;

[Required]
public string Path
{
get { return _path; }
set { _path = value; }
}
public string Path { get; set; }

public string Include
{
get { return _include; }
set { _include = value; }
}
public string Include { get; set; }

public string IncludeDirectory
{
get { return _includeDirectory; }
set { _includeDirectory = value; }
}
public string IncludeDirectory { get; set; }

public string Exclude
{
get { return _exclude; }
set { _exclude = value; }
}
public string Exclude { get; set; }

public string ExcludeByFile
{
get { return _excludeByFile; }
set { _excludeByFile = value; }
}
public string ExcludeByFile { get; set; }

public string ExcludeByAttribute
{
get { return _excludeByAttribute; }
set { _excludeByAttribute = value; }
}
public string ExcludeByAttribute { get; set; }

public bool IncludeTestAssembly
{
get { return _includeTestAssembly; }
set { _includeTestAssembly = value; }
}
public bool IncludeTestAssembly { get; set; }

public bool SingleHit
{
get { return _singleHit; }
set { _singleHit = value; }
}
public bool SingleHit { get; set; }

public string MergeWith
{
get { return _mergeWith; }
set { _mergeWith = value; }
}
public string MergeWith { get; set; }

public bool UseSourceLink
{
get { return _useSourceLink; }
set { _useSourceLink = value; }
}
public bool UseSourceLink { get; set; }

public bool SkipAutoProps { get; set; }

[Output]
public ITaskItem InstrumenterState
{
get { return _instrumenterState; }
set { _instrumenterState = value; }
}
public ITaskItem InstrumenterState { get; set; }

public InstrumentationTask()
{
@@ -129,7 +76,7 @@ public override bool Execute()
serviceCollection.AddTransient<ILogger, MSBuildLogger>(_ => _logger);
serviceCollection.AddTransient<IRetryHelper, RetryHelper>();
// We cache resolutions
serviceCollection.AddSingleton<ISourceRootTranslator, SourceRootTranslator>(serviceProvider => new SourceRootTranslator(_path, serviceProvider.GetRequiredService<ILogger>(), serviceProvider.GetRequiredService<IFileSystem>()));
serviceCollection.AddSingleton<ISourceRootTranslator, SourceRootTranslator>(serviceProvider => new SourceRootTranslator(Path, serviceProvider.GetRequiredService<ILogger>(), serviceProvider.GetRequiredService<IFileSystem>()));
// We need to keep singleton/static semantics
serviceCollection.AddSingleton<IInstrumentationHelper, InstrumentationHelper>();
serviceCollection.AddSingleton<ICecilSymbolHelper, CecilSymbolHelper>();
@@ -142,18 +89,19 @@ public override bool Execute()

CoverageParameters parameters = new CoverageParameters
{
IncludeFilters = _include?.Split(','),
IncludeDirectories = _includeDirectory?.Split(','),
ExcludeFilters = _exclude?.Split(','),
ExcludedSourceFiles = _excludeByFile?.Split(','),
ExcludeAttributes = _excludeByAttribute?.Split(','),
IncludeTestAssembly = _includeTestAssembly,
SingleHit = _singleHit,
MergeWith = _mergeWith,
UseSourceLink = _useSourceLink
IncludeFilters = Include?.Split(','),
IncludeDirectories = IncludeDirectory?.Split(','),
ExcludeFilters = Exclude?.Split(','),
ExcludedSourceFiles = ExcludeByFile?.Split(','),
ExcludeAttributes = ExcludeByAttribute?.Split(','),
IncludeTestAssembly = IncludeTestAssembly,
SingleHit = SingleHit,
MergeWith = MergeWith,
UseSourceLink = UseSourceLink,
SkipAutoProps = SkipAutoProps
};

Coverage coverage = new Coverage(_path,
Coverage coverage = new Coverage(Path,
parameters,
_logger,
ServiceProvider.GetService<IInstrumentationHelper>(),
5 changes: 3 additions & 2 deletions src/coverlet.msbuild.tasks/coverlet.msbuild.targets
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@

<Target Name="ReferencedPathMaps" BeforeTargets="CoreCompile" DependsOnTargets="ResolveProjectReferences" >
<MSBuild Projects="@(AnnotatedProjects->'%(FullPath)')"
Targets="CoverletGetPathMap"
Targets="CoverletGetPathMap"
Properties="TargetFramework=%(AnnotatedProjects.NearestTargetFramework)"
SkipNonexistentTargets="true">
<Output TaskParameter="TargetOutputs"
@@ -38,7 +38,8 @@
IncludeTestAssembly="$(IncludeTestAssembly)"
SingleHit="$(SingleHit)"
MergeWith="$(MergeWith)"
UseSourceLink="$(UseSourceLink)" >
UseSourceLink="$(UseSourceLink)"
SkipAutoProps="$(SkipAutoProps)" >
<Output TaskParameter="InstrumenterState" PropertyName="InstrumenterState"/>
</Coverlet.MSbuild.Tasks.InstrumentationTask>
</Target>
6 changes: 4 additions & 2 deletions test/coverlet.collector.tests/CoverletSettingsParserTests.cs
Original file line number Diff line number Diff line change
@@ -73,6 +73,7 @@ public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters,
this.CreateCoverletNodes(doc, configElement, CoverletConstants.UseSourceLinkElementName, "false");
this.CreateCoverletNodes(doc, configElement, CoverletConstants.SingleHitElementName, "true");
this.CreateCoverletNodes(doc, configElement, CoverletConstants.IncludeTestAssemblyElementName, "true");
this.CreateCoverletNodes(doc, configElement, CoverletConstants.SkipAutoProps, "true");

CoverletSettings coverletSettings = _coverletSettingsParser.Parse(configElement, testModules);

@@ -90,10 +91,11 @@ public void ParseShouldCorrectlyParseConfigurationElement(string includeFilters,
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);
Assert.True(coverletSettings.SkipAutoProps);
}

[Fact]
@@ -104,7 +106,7 @@ public void ParseShouldCorrectlyParseConfigurationElementWithNullInnerText()
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.IncludeDirectoriesElementName);
this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeSourceFilesElementName);
this.CreateCoverleteNullInnerTextNodes(doc, configElement, CoverletConstants.ExcludeAttributesElementName);

54 changes: 54 additions & 0 deletions test/coverlet.core.tests/Coverage/CoverageTests.AutoProps.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using System.IO;
using System.Threading.Tasks;
using Coverlet.Core.Samples.Tests;
using Xunit;

namespace Coverlet.Core.Tests
{
public partial class CoverageTests
{
[Theory]
[InlineData(true)]
[InlineData(false)]
public void SkipAutoProps(bool skipAutoProps)
{
string path = Path.GetTempFileName();
try
{
FunctionExecutor.Run(async (string[] parameters) =>
{
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<AutoProps>(instance =>
{
instance.AutoPropsNonInit = 10;
instance.AutoPropsInit = 20;
int readVal = instance.AutoPropsNonInit;
readVal = instance.AutoPropsInit;
return Task.CompletedTask;
},
persistPrepareResultToFile: parameters[0], skipAutoProps: bool.Parse(parameters[1]));

return 0;
}, new string[] { path, skipAutoProps.ToString() });

if (skipAutoProps)
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 12, 12)
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 11)
.AssertLinesCovered(BuildConfiguration.Debug, (13, 1));
}
else
{
TestInstrumentationHelper.GetCoverageResult(path)
.Document("Instrumentation.AutoProps.cs")
.AssertLinesCoveredFromTo(BuildConfiguration.Debug, 7, 13);
}
}
finally
{
File.Delete(path);
}
}
}
}
10 changes: 8 additions & 2 deletions test/coverlet.core.tests/Coverage/InstrumenterHelper.cs
Original file line number Diff line number Diff line change
@@ -67,7 +67,12 @@ public static CoverageResult GetCoverageResult(string filePath)
return coverage.GetCoverageResult();
}

async public static Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callMethod, Func<string, string[]> includeFilter = null, Func<string, string[]> excludeFilter = null, string persistPrepareResultToFile = null, bool disableRestoreModules = false)
async public static Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callMethod,
Func<string, string[]> includeFilter = null,
Func<string, string[]> excludeFilter = null,
string persistPrepareResultToFile = null,
bool disableRestoreModules = false,
bool skipAutoProps = false)
{
if (persistPrepareResultToFile is null)
{
@@ -105,7 +110,8 @@ async public static Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callM
IncludeTestAssembly = true,
SingleHit = false,
MergeWith = string.Empty,
UseSourceLink = false
UseSourceLink = false,
SkipAutoProps = skipAutoProps
};

// Instrument module
14 changes: 7 additions & 7 deletions test/coverlet.core.tests/Instrumentation/InstrumenterTests.cs
Original file line number Diff line number Diff line change
@@ -77,7 +77,7 @@ public void TestCoreLibInstrumentation()
InstrumentationHelper instrumentationHelper =
new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object, _mockLogger.Object, sourceRootTranslator);
Instrumenter instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
Array.Empty<string>(), false, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator, new CecilSymbolHelper());
Array.Empty<string>(), false, false, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object, sourceRootTranslator, new CecilSymbolHelper());

Assert.True(instrumenter.CanInstrument());
InstrumenterResult result = instrumenter.Instrument();
@@ -242,7 +242,7 @@ private InstrumenterTest CreateInstrumentor(bool fakeCoreLibModule = false, stri
new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock<ILogger>().Object, new SourceRootTranslator(new Mock<ILogger>().Object, new FileSystem()));

module = Path.Combine(directory.FullName, destModule);
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore, false,
Instrumenter instrumenter = new Instrumenter(module, identifier, Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), attributesToIgnore, false, false,
_mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(_mockLogger.Object, new FileSystem()), new CecilSymbolHelper());
return new InstrumenterTest
{
@@ -420,7 +420,7 @@ public void SkipEmbeddedPpdbWithoutLocalSource()
new SourceRootTranslator(xunitDll, new Mock<ILogger>().Object, new FileSystem()));

Instrumenter instrumenter = new Instrumenter(xunitDll, "_xunit_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
Array.Empty<string>(), false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(xunitDll, loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(xunitDll, loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Assert.True(instrumentationHelper.HasPdb(xunitDll, out bool embedded));
Assert.True(embedded);
Assert.False(instrumenter.CanInstrument());
@@ -433,7 +433,7 @@ public void SkipEmbeddedPpdbWithoutLocalSource()
new SourceRootTranslator(sample, new Mock<ILogger>().Object, new FileSystem()));

instrumenter = new Instrumenter(sample, "_coverlet_tests_projectsample_empty", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
Array.Empty<string>(), false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(sample, loggerMock.Object, new FileSystem()), new CecilSymbolHelper());

Assert.True(instrumentationHelper.HasPdb(sample, out embedded));
Assert.False(embedded);
@@ -479,7 +479,7 @@ public void SkipPpdbWithoutLocalSource()
string sample = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), dllFileName).First();
var loggerMock = new Mock<ILogger>();
Instrumenter instrumenter = new Instrumenter(sample, "_75d9f96508d74def860a568f426ea4a4_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
Array.Empty<string>(), false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Assert.True(instrumentationHelper.HasPdb(sample, out bool embedded));
Assert.False(embedded);
Assert.False(instrumenter.CanInstrument());
@@ -496,7 +496,7 @@ public void TestInstrument_MissingModule()
new SourceRootTranslator(new Mock<ILogger>().Object, new FileSystem()));

var instrumenter = new Instrumenter("test", "_test_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
Array.Empty<string>(), false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Assert.False(instrumenter.CanInstrument());
loggerMock.Verify(l => l.LogWarning(It.IsAny<string>()));
}
@@ -519,7 +519,7 @@ public void TestInstrument_AssemblyMarkedAsExcludeFromCodeCoverage()
new SourceRootTranslator(new Mock<ILogger>().Object, new FileSystem()));

Instrumenter instrumenter = new Instrumenter(excludedbyattributeDll, "_xunit_excludedbyattribute", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(),
Array.Empty<string>(), false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
Array.Empty<string>(), false, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem()), new CecilSymbolHelper());
InstrumenterResult result = instrumenter.Instrument();
Assert.Empty(result.Documents);
loggerMock.Verify(l => l.LogVerbose(It.IsAny<string>()));
15 changes: 15 additions & 0 deletions test/coverlet.core.tests/Samples/Instrumentation.AutoProps.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using System;

namespace Coverlet.Core.Samples.Tests
{
public class AutoProps
{
private int _myVal = 0;
public AutoProps()
{
_myVal = new Random().Next();
}
public int AutoPropsNonInit { get; set; }
public int AutoPropsInit { get; set; } = 10;
}
}