Increase severity of some rules and fix instances (#1305)

Increase severity of some rules and fix instances
This commit is contained in:
Amaury Levé
2022-02-24 11:23:41 +01:00
committed by GitHub
parent 76ffde3624
commit 2bb04f21ec
25 changed files with 400 additions and 422 deletions
+21 -3
View File
@@ -87,20 +87,34 @@ dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
dotnet_style_prefer_auto_properties = true:warning dotnet_style_prefer_auto_properties = true:warning
dotnet_style_prefer_conditional_expression_over_assignment = true:silent dotnet_style_prefer_conditional_expression_over_assignment = true:silent
dotnet_style_prefer_conditional_expression_over_return = true:silent dotnet_style_prefer_conditional_expression_over_return = true:silent
# CA1805: Do not initialize unnecessarily
dotnet_diagnostic.CA1805.severity = warning
# IDE0063: Use simple 'using' statement
dotnet_diagnostic.IDE0063.severity = warning
# IDE0057: Use range operator
dotnet_diagnostic.IDE0057.severity = warning
# IDE0075: Simplify conditional expression
dotnet_diagnostic.IDE0075.severity = warning
# IDE0071: Simplify interpolation
dotnet_diagnostic.IDE0071.severity = warning
# CA1829: Use Length/Count property instead of Count() when available
dotnet_diagnostic.CA1829.severity = warning
# CA1827: Do not use Count() or LongCount() when Any() can be used
dotnet_diagnostic.CA1827.severity = warning
############################### ###############################
# Naming Conventions # # Naming Conventions #
############################### ###############################
# Name all constant fields using PascalCase # Name all constant fields using PascalCase
dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion dotnet_naming_rule.constant_fields_should_be_pascal_case.severity = suggestion
dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields dotnet_naming_rule.constant_fields_should_be_pascal_case.symbols = constant_fields
dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style dotnet_naming_rule.constant_fields_should_be_pascal_case.style = pascal_case_style
dotnet_naming_symbols.constant_fields.applicable_kinds = field dotnet_naming_symbols.constant_fields.applicable_kinds = field
dotnet_naming_symbols.constant_fields.required_modifiers = const dotnet_naming_symbols.constant_fields.required_modifiers = const
dotnet_naming_style.pascal_case_style.capitalization = pascal_case dotnet_naming_style.pascal_case_style.capitalization = pascal_case
# Static fields should have s_ prefix # Static fields should have s_ prefix
dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion dotnet_naming_rule.static_fields_should_have_prefix.severity = suggestion
dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields dotnet_naming_rule.static_fields_should_have_prefix.symbols = static_fields
dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style dotnet_naming_rule.static_fields_should_have_prefix.style = static_prefix_style
dotnet_naming_symbols.static_fields.applicable_kinds = field dotnet_naming_symbols.static_fields.applicable_kinds = field
dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_symbols.static_fields.required_modifiers = static
dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected dotnet_naming_symbols.static_fields.applicable_accessibilities = private, internal, private_protected
@@ -109,11 +123,15 @@ dotnet_naming_style.static_prefix_style.capitalization = camel_case
# Internal and private fields should be _camelCase # Internal and private fields should be _camelCase
dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion dotnet_naming_rule.camel_case_for_private_internal_fields.severity = suggestion
dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields dotnet_naming_rule.camel_case_for_private_internal_fields.symbols = private_internal_fields
dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style dotnet_naming_rule.camel_case_for_private_internal_fields.style = camel_case_underscore_style
dotnet_naming_symbols.private_internal_fields.applicable_kinds = field dotnet_naming_symbols.private_internal_fields.applicable_kinds = field
dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal dotnet_naming_symbols.private_internal_fields.applicable_accessibilities = private, internal
dotnet_naming_style.camel_case_underscore_style.required_prefix = _ dotnet_naming_style.camel_case_underscore_style.required_prefix = _
dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case dotnet_naming_style.camel_case_underscore_style.capitalization = camel_case
# IDE1006: Naming Styles
dotnet_diagnostic.IDE1006.severity = warning
# IDE0090: Use 'new(...)'
dotnet_diagnostic.IDE0090.severity = warning
############################### ###############################
# C# Coding Conventions # # C# Coding Conventions #
############################### ###############################
@@ -163,15 +163,13 @@ namespace Coverlet.Collector.DataCollection
// Get coverage reports // Get coverage reports
IEnumerable<(string report, string fileName)> coverageReports = _coverageManager?.GetCoverageReports(); IEnumerable<(string report, string fileName)> coverageReports = _coverageManager?.GetCoverageReports();
if (coverageReports != null && coverageReports.Count() > 0) if (coverageReports != null && coverageReports.Any())
{ {
// Send result attachments to test platform. // Send result attachments to test platform.
using (var attachmentManager = new AttachmentManager(_dataSink, _dataCollectionContext, _logger, _eqtTrace, _countDownEventFactory.Create(coverageReports.Count(), TimeSpan.FromSeconds(30)))) using var attachmentManager = new AttachmentManager(_dataSink, _dataCollectionContext, _logger, _eqtTrace, _countDownEventFactory.Create(coverageReports.Count(), TimeSpan.FromSeconds(30)));
foreach ((string report, string fileName) in coverageReports)
{ {
foreach ((string report, string fileName) in coverageReports) attachmentManager.SendCoverageReport(report, fileName);
{
attachmentManager.SendCoverageReport(report, fileName);
}
} }
} }
else else
@@ -66,7 +66,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="testModules">Test modules</param> /// <param name="testModules">Test modules</param>
/// <returns>Test module</returns> /// <returns>Test module</returns>
private string ParseTestModule(IEnumerable<string> testModules) private static string ParseTestModule(IEnumerable<string> testModules)
{ {
// Validate if at least one source present. // Validate if at least one source present.
if (testModules == null || !testModules.Any()) if (testModules == null || !testModules.Any())
@@ -86,7 +86,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Report formats</returns> /// <returns>Report formats</returns>
private string[] ParseReportFormats(XmlElement configurationElement) private static string[] ParseReportFormats(XmlElement configurationElement)
{ {
string[] formats = Array.Empty<string>(); string[] formats = Array.Empty<string>();
if (configurationElement != null) if (configurationElement != null)
@@ -103,7 +103,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Filters to include</returns> /// <returns>Filters to include</returns>
private string[] ParseIncludeFilters(XmlElement configurationElement) private static string[] ParseIncludeFilters(XmlElement configurationElement)
{ {
XmlElement includeFiltersElement = configurationElement[CoverletConstants.IncludeFiltersElementName]; XmlElement includeFiltersElement = configurationElement[CoverletConstants.IncludeFiltersElementName];
return SplitElement(includeFiltersElement); return SplitElement(includeFiltersElement);
@@ -114,7 +114,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Directories to include</returns> /// <returns>Directories to include</returns>
private string[] ParseIncludeDirectories(XmlElement configurationElement) private static string[] ParseIncludeDirectories(XmlElement configurationElement)
{ {
XmlElement includeDirectoriesElement = configurationElement[CoverletConstants.IncludeDirectoriesElementName]; XmlElement includeDirectoriesElement = configurationElement[CoverletConstants.IncludeDirectoriesElementName];
return SplitElement(includeDirectoriesElement); return SplitElement(includeDirectoriesElement);
@@ -125,7 +125,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Filters to exclude</returns> /// <returns>Filters to exclude</returns>
private string[] ParseExcludeFilters(XmlElement configurationElement) private static string[] ParseExcludeFilters(XmlElement configurationElement)
{ {
var excludeFilters = new List<string> { CoverletConstants.DefaultExcludeFilter }; var excludeFilters = new List<string> { CoverletConstants.DefaultExcludeFilter };
@@ -147,7 +147,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Source files to exclude</returns> /// <returns>Source files to exclude</returns>
private string[] ParseExcludeSourceFiles(XmlElement configurationElement) private static string[] ParseExcludeSourceFiles(XmlElement configurationElement)
{ {
XmlElement excludeSourceFilesElement = configurationElement[CoverletConstants.ExcludeSourceFilesElementName]; XmlElement excludeSourceFilesElement = configurationElement[CoverletConstants.ExcludeSourceFilesElementName];
return SplitElement(excludeSourceFilesElement); return SplitElement(excludeSourceFilesElement);
@@ -158,7 +158,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Attributes to exclude</returns> /// <returns>Attributes to exclude</returns>
private string[] ParseExcludeAttributes(XmlElement configurationElement) private static string[] ParseExcludeAttributes(XmlElement configurationElement)
{ {
XmlElement excludeAttributesElement = configurationElement[CoverletConstants.ExcludeAttributesElementName]; XmlElement excludeAttributesElement = configurationElement[CoverletConstants.ExcludeAttributesElementName];
return SplitElement(excludeAttributesElement); return SplitElement(excludeAttributesElement);
@@ -169,7 +169,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Merge with attribute</returns> /// <returns>Merge with attribute</returns>
private string ParseMergeWith(XmlElement configurationElement) private static string ParseMergeWith(XmlElement configurationElement)
{ {
XmlElement mergeWithElement = configurationElement[CoverletConstants.MergeWithElementName]; XmlElement mergeWithElement = configurationElement[CoverletConstants.MergeWithElementName];
return mergeWithElement?.InnerText; return mergeWithElement?.InnerText;
@@ -180,7 +180,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Use source link flag</returns> /// <returns>Use source link flag</returns>
private bool ParseUseSourceLink(XmlElement configurationElement) private static bool ParseUseSourceLink(XmlElement configurationElement)
{ {
XmlElement useSourceLinkElement = configurationElement[CoverletConstants.UseSourceLinkElementName]; XmlElement useSourceLinkElement = configurationElement[CoverletConstants.UseSourceLinkElementName];
bool.TryParse(useSourceLinkElement?.InnerText, out bool useSourceLink); bool.TryParse(useSourceLinkElement?.InnerText, out bool useSourceLink);
@@ -192,7 +192,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Single hit flag</returns> /// <returns>Single hit flag</returns>
private bool ParseSingleHit(XmlElement configurationElement) private static bool ParseSingleHit(XmlElement configurationElement)
{ {
XmlElement singleHitElement = configurationElement[CoverletConstants.SingleHitElementName]; XmlElement singleHitElement = configurationElement[CoverletConstants.SingleHitElementName];
bool.TryParse(singleHitElement?.InnerText, out bool singleHit); bool.TryParse(singleHitElement?.InnerText, out bool singleHit);
@@ -204,7 +204,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>ParseDeterministicReport flag</returns> /// <returns>ParseDeterministicReport flag</returns>
private bool ParseDeterministicReport(XmlElement configurationElement) private static bool ParseDeterministicReport(XmlElement configurationElement)
{ {
XmlElement deterministicReportElement = configurationElement[CoverletConstants.DeterministicReport]; XmlElement deterministicReportElement = configurationElement[CoverletConstants.DeterministicReport];
bool.TryParse(deterministicReportElement?.InnerText, out bool deterministicReport); bool.TryParse(deterministicReportElement?.InnerText, out bool deterministicReport);
@@ -216,7 +216,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Include Test Assembly Flag</returns> /// <returns>Include Test Assembly Flag</returns>
private bool ParseIncludeTestAssembly(XmlElement configurationElement) private static bool ParseIncludeTestAssembly(XmlElement configurationElement)
{ {
XmlElement includeTestAssemblyElement = configurationElement[CoverletConstants.IncludeTestAssemblyElementName]; XmlElement includeTestAssemblyElement = configurationElement[CoverletConstants.IncludeTestAssemblyElementName];
bool.TryParse(includeTestAssemblyElement?.InnerText, out bool includeTestAssembly); bool.TryParse(includeTestAssemblyElement?.InnerText, out bool includeTestAssembly);
@@ -228,7 +228,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>Include Test Assembly Flag</returns> /// <returns>Include Test Assembly Flag</returns>
private bool ParseSkipAutoProps(XmlElement configurationElement) private static bool ParseSkipAutoProps(XmlElement configurationElement)
{ {
XmlElement skipAutoPropsElement = configurationElement[CoverletConstants.SkipAutoProps]; XmlElement skipAutoPropsElement = configurationElement[CoverletConstants.SkipAutoProps];
bool.TryParse(skipAutoPropsElement?.InnerText, out bool skipAutoProps); bool.TryParse(skipAutoPropsElement?.InnerText, out bool skipAutoProps);
@@ -240,7 +240,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="configurationElement">Configuration element</param> /// <param name="configurationElement">Configuration element</param>
/// <returns>DoesNotReturn attributes</returns> /// <returns>DoesNotReturn attributes</returns>
private string[] ParseDoesNotReturnAttributes(XmlElement configurationElement) private static string[] ParseDoesNotReturnAttributes(XmlElement configurationElement)
{ {
XmlElement doesNotReturnAttributesElement = configurationElement[CoverletConstants.DoesNotReturnAttributesElementName]; XmlElement doesNotReturnAttributesElement = configurationElement[CoverletConstants.DoesNotReturnAttributesElementName];
return SplitElement(doesNotReturnAttributesElement); return SplitElement(doesNotReturnAttributesElement);
@@ -251,7 +251,7 @@ namespace Coverlet.Collector.DataCollection
/// </summary> /// </summary>
/// <param name="element">The element to split</param> /// <param name="element">The element to split</param>
/// <returns>An array of the values in the element</returns> /// <returns>An array of the values in the element</returns>
private string[] SplitElement(XmlElement element) private static string[] SplitElement(XmlElement element)
{ {
return element?.InnerText?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(value => !string.IsNullOrWhiteSpace(value)).Select(value => value.Trim()).ToArray(); return element?.InnerText?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Where(value => !string.IsNullOrWhiteSpace(value)).Select(value => value.Trim()).ToArray();
} }
@@ -17,7 +17,7 @@ namespace Coverlet.Collector.DataCollection
public class CoverletInProcDataCollector : InProcDataCollection public class CoverletInProcDataCollector : InProcDataCollection
{ {
private TestPlatformEqtTrace _eqtTrace; private TestPlatformEqtTrace _eqtTrace;
private bool _enableExceptionLog = false; private bool _enableExceptionLog;
private void AttachDebugger() private void AttachDebugger()
{ {
@@ -9,7 +9,8 @@ namespace Coverlet.Console.Logging
{ {
class ConsoleLogger : ILogger class ConsoleLogger : ILogger
{ {
private static readonly object _sync = new object(); private static readonly object s_sync = new();
public LogLevel Level { get; set; } = LogLevel.Normal; public LogLevel Level { get; set; } = LogLevel.Normal;
public void LogError(string message) => Log(LogLevel.Quiet, message, ConsoleColor.Red); public void LogError(string message) => Log(LogLevel.Quiet, message, ConsoleColor.Red);
@@ -26,7 +27,7 @@ namespace Coverlet.Console.Logging
{ {
if (level < Level) return; if (level < Level) return;
lock (_sync) lock (s_sync)
{ {
ConsoleColor currentForegroundColor; ConsoleColor currentForegroundColor;
if (color != (currentForegroundColor = ForegroundColor)) if (color != (currentForegroundColor = ForegroundColor))
+1 -1
View File
@@ -330,7 +330,7 @@ namespace Coverlet.Core
return false; return false;
} }
private Method GetMethodWithSameLineInSameDocument(Classes documentClasses, string compilerGeneratedClassName, int branchLine) private static Method GetMethodWithSameLineInSameDocument(Classes documentClasses, string compilerGeneratedClassName, int branchLine)
{ {
foreach (KeyValuePair<string, Methods> @class in documentClasses) foreach (KeyValuePair<string, Methods> @class in documentClasses)
{ {
@@ -18,7 +18,7 @@ namespace Coverlet.Core.Helpers
internal class InstrumentationHelper : IInstrumentationHelper internal class InstrumentationHelper : IInstrumentationHelper
{ {
private const int RetryAttempts = 12; private const int RetryAttempts = 12;
private readonly ConcurrentDictionary<string, string> _backupList = new ConcurrentDictionary<string, string>(); private readonly ConcurrentDictionary<string, string> _backupList = new();
private readonly IRetryHelper _retryHelper; private readonly IRetryHelper _retryHelper;
private readonly IFileSystem _fileSystem; private readonly IFileSystem _fileSystem;
private readonly ISourceRootTranslator _sourceRootTranslator; private readonly ISourceRootTranslator _sourceRootTranslator;
@@ -83,27 +83,25 @@ namespace Coverlet.Core.Helpers
public bool HasPdb(string module, out bool embedded) public bool HasPdb(string module, out bool embedded)
{ {
embedded = false; embedded = false;
using (Stream moduleStream = _fileSystem.OpenRead(module)) using Stream moduleStream = _fileSystem.OpenRead(module);
using (var peReader = new PEReader(moduleStream)) using var peReader = new PEReader(moduleStream);
foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory())
{ {
foreach (DebugDirectoryEntry entry in peReader.ReadDebugDirectory()) if (entry.Type == DebugDirectoryEntryType.CodeView)
{ {
if (entry.Type == DebugDirectoryEntryType.CodeView) CodeViewDebugDirectoryData codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry);
if (_sourceRootTranslator.ResolveFilePath(codeViewData.Path) == $"{Path.GetFileNameWithoutExtension(module)}.pdb")
{ {
CodeViewDebugDirectoryData codeViewData = peReader.ReadCodeViewDebugDirectoryData(entry); // PDB is embedded
if (_sourceRootTranslator.ResolveFilePath(codeViewData.Path) == $"{Path.GetFileNameWithoutExtension(module)}.pdb") embedded = true;
{ return true;
// PDB is embedded
embedded = true;
return true;
}
return _fileSystem.Exists(_sourceRootTranslator.ResolveFilePath(codeViewData.Path));
} }
}
return false; return _fileSystem.Exists(_sourceRootTranslator.ResolveFilePath(codeViewData.Path));
}
} }
return false;
} }
public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNotFoundDocument) public bool EmbeddedPortablePdbHasLocalSource(string module, out string firstNotFoundDocument)
@@ -116,17 +114,15 @@ namespace Coverlet.Core.Helpers
{ {
if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb) if (entry.Type == DebugDirectoryEntryType.EmbeddedPortablePdb)
{ {
using (MetadataReaderProvider embeddedMetadataProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry)) using MetadataReaderProvider embeddedMetadataProvider = peReader.ReadEmbeddedPortablePdbDebugDirectoryData(entry);
MetadataReader metadataReader = embeddedMetadataProvider.GetMetadataReader();
(bool allDocumentsMatch, string notFoundDocument) = MatchDocumentsWithSources(metadataReader);
if (!allDocumentsMatch)
{ {
MetadataReader metadataReader = embeddedMetadataProvider.GetMetadataReader(); firstNotFoundDocument = notFoundDocument;
return false;
(bool allDocumentsMatch, string notFoundDocument) = MatchDocumentsWithSources(metadataReader);
if (!allDocumentsMatch)
{
firstNotFoundDocument = notFoundDocument;
return false;
}
} }
} }
} }
@@ -388,7 +384,7 @@ namespace Coverlet.Core.Helpers
_logger = logger; _logger = logger;
} }
private bool IsTypeFilterMatch(string module, string type, string[] filters) private static bool IsTypeFilterMatch(string module, string type, string[] filters)
{ {
Debug.Assert(module != null); Debug.Assert(module != null);
Debug.Assert(filters != null); Debug.Assert(filters != null);
@@ -408,7 +404,7 @@ namespace Coverlet.Core.Helpers
return false; return false;
} }
private string GetBackupPath(string module, string identifier) private static string GetBackupPath(string module, string identifier)
{ {
return Path.Combine( return Path.Combine(
Path.GetTempPath(), Path.GetTempPath(),
@@ -428,14 +424,14 @@ namespace Coverlet.Core.Helpers
return retryStrategy; return retryStrategy;
} }
private string WildcardToRegex(string pattern) private static string WildcardToRegex(string pattern)
{ {
return "^" + Regex.Escape(pattern). return "^" + Regex.Escape(pattern).
Replace("\\*", ".*"). Replace("\\*", ".*").
Replace("\\?", "?") + "$"; Replace("\\?", "?") + "$";
} }
private bool IsAssembly(string filePath) private static bool IsAssembly(string filePath)
{ {
Debug.Assert(filePath != null); Debug.Assert(filePath != null);
@@ -453,7 +449,7 @@ namespace Coverlet.Core.Helpers
} }
} }
private bool IsUnknownModuleInFSharpAssembly(Guid languageGuid, string docName) private static bool IsUnknownModuleInFSharpAssembly(Guid languageGuid, string docName)
{ {
// https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#document-table-0x30 // https://github.com/dotnet/runtime/blob/main/docs/design/specs/PortablePdb-Metadata.md#document-table-0x30
return languageGuid.Equals(new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3")) return languageGuid.Equals(new Guid("ab4f38c9-b6e6-43ba-be3b-58080b2ccce3"))
@@ -48,7 +48,7 @@ namespace Coverlet.Core.Helpers
_sourceToDeterministicPathMapping = LoadSourceToDeterministicPathMapping(_sourceRootMapping); _sourceToDeterministicPathMapping = LoadSourceToDeterministicPathMapping(_sourceRootMapping);
} }
private Dictionary<string, List<string>> LoadSourceToDeterministicPathMapping(Dictionary<string, List<SourceRootMapping>> sourceRootMapping) private static Dictionary<string, List<string>> LoadSourceToDeterministicPathMapping(Dictionary<string, List<SourceRootMapping>> sourceRootMapping)
{ {
if (sourceRootMapping is null) if (sourceRootMapping is null)
{ {
@@ -17,21 +17,21 @@ namespace Coverlet.Core.Instrumentation
/// In case of testing different runtime i.e. netfx we could find netstandard.dll in folder. /// In case of testing different runtime i.e. netfx we could find netstandard.dll in folder.
/// netstandard.dll is a forward only lib, there is no IL but only forwards to "runtime" implementation. /// netstandard.dll is a forward only lib, there is no IL but only forwards to "runtime" implementation.
/// For some classes implementation are in different assembly for different runtime for instance: /// For some classes implementation are in different assembly for different runtime for instance:
/// ///
/// For NetFx 4.7 /// For NetFx 4.7
/// // Token: 0x2700072C RID: 1836 /// // Token: 0x2700072C RID: 1836
/// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName /// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName
/// { /// {
/// .assembly extern System /// .assembly extern System
/// } /// }
/// ///
/// For netcoreapp2.2 /// For netcoreapp2.2
/// Token: 0x2700072C RID: 1836 /// Token: 0x2700072C RID: 1836
/// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName /// .class extern forwarder System.Security.Cryptography.X509Certificates.StoreName
/// { /// {
/// .assembly extern System.Security.Cryptography.X509Certificates /// .assembly extern System.Security.Cryptography.X509Certificates
/// } /// }
/// ///
/// There is a concrete possibility that Cecil cannot find implementation and throws StackOverflow exception https://github.com/jbevain/cecil/issues/575 /// There is a concrete possibility that Cecil cannot find implementation and throws StackOverflow exception https://github.com/jbevain/cecil/issues/575
/// This custom resolver check if requested lib is a "official" netstandard.dll and load once of "current runtime" with /// This custom resolver check if requested lib is a "official" netstandard.dll and load once of "current runtime" with
/// correct forwards. /// correct forwards.
@@ -39,10 +39,10 @@ namespace Coverlet.Core.Instrumentation
/// </summary> /// </summary>
internal class NetstandardAwareAssemblyResolver : DefaultAssemblyResolver internal class NetstandardAwareAssemblyResolver : DefaultAssemblyResolver
{ {
private static readonly System.Reflection.Assembly _netStandardAssembly; private static readonly System.Reflection.Assembly s_netStandardAssembly;
private static readonly string _name; private static readonly string s_name;
private static readonly byte[] _publicKeyToken; private static readonly byte[] s_publicKeyToken;
private static readonly AssemblyDefinition _assemblyDefinition; private static readonly AssemblyDefinition s_assemblyDefinition;
private readonly string _modulePath; private readonly string _modulePath;
private readonly Lazy<CompositeCompilationAssemblyResolver> _compositeResolver; private readonly Lazy<CompositeCompilationAssemblyResolver> _compositeResolver;
@@ -53,11 +53,11 @@ namespace Coverlet.Core.Instrumentation
try try
{ {
// To be sure to load information of "real" runtime netstandard implementation // To be sure to load information of "real" runtime netstandard implementation
_netStandardAssembly = System.Reflection.Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll")); s_netStandardAssembly = System.Reflection.Assembly.LoadFile(Path.Combine(Path.GetDirectoryName(typeof(object).Assembly.Location), "netstandard.dll"));
System.Reflection.AssemblyName name = _netStandardAssembly.GetName(); System.Reflection.AssemblyName name = s_netStandardAssembly.GetName();
_name = name.Name; s_name = name.Name;
_publicKeyToken = name.GetPublicKeyToken(); s_publicKeyToken = name.GetPublicKeyToken();
_assemblyDefinition = AssemblyDefinition.ReadAssembly(_netStandardAssembly.Location); s_assemblyDefinition = AssemblyDefinition.ReadAssembly(s_netStandardAssembly.Location);
} }
catch (FileNotFoundException) catch (FileNotFoundException)
{ {
@@ -70,7 +70,7 @@ namespace Coverlet.Core.Instrumentation
_modulePath = modulePath; _modulePath = modulePath;
_logger = logger; _logger = logger;
// this is lazy because we cannot create AspNetCoreSharedFrameworkResolver if not on .NET Core runtime, // this is lazy because we cannot create AspNetCoreSharedFrameworkResolver if not on .NET Core runtime,
// runtime folders are different // runtime folders are different
_compositeResolver = new Lazy<CompositeCompilationAssemblyResolver>(() => new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[] _compositeResolver = new Lazy<CompositeCompilationAssemblyResolver>(() => new CompositeCompilationAssemblyResolver(new ICompilationAssemblyResolver[]
{ {
@@ -82,26 +82,26 @@ namespace Coverlet.Core.Instrumentation
} }
// Check name and public key but not version that could be different // Check name and public key but not version that could be different
private bool CheckIfSearchingNetstandard(AssemblyNameReference name) private static bool CheckIfSearchingNetstandard(AssemblyNameReference name)
{ {
if (_netStandardAssembly is null) if (s_netStandardAssembly is null)
{ {
return false; return false;
} }
if (_name != name.Name) if (s_name != name.Name)
{ {
return false; return false;
} }
if (name.PublicKeyToken.Length != _publicKeyToken.Length) if (name.PublicKeyToken.Length != s_publicKeyToken.Length)
{ {
return false; return false;
} }
for (int i = 0; i < name.PublicKeyToken.Length; i++) for (int i = 0; i < name.PublicKeyToken.Length; i++)
{ {
if (_publicKeyToken[i] != name.PublicKeyToken[i]) if (s_publicKeyToken[i] != name.PublicKeyToken[i])
{ {
return false; return false;
} }
@@ -114,7 +114,7 @@ namespace Coverlet.Core.Instrumentation
{ {
if (CheckIfSearchingNetstandard(name)) if (CheckIfSearchingNetstandard(name))
{ {
return _assemblyDefinition; return s_assemblyDefinition;
} }
else else
{ {
@@ -136,21 +136,21 @@ namespace Coverlet.Core.Instrumentation
} }
} }
private bool IsDotNetCore() private static bool IsDotNetCore()
{ {
// object for .NET Framework is inside mscorlib.dll // object for .NET Framework is inside mscorlib.dll
return Path.GetFileName(typeof(object).Assembly.Location) == "System.Private.CoreLib.dll"; return Path.GetFileName(typeof(object).Assembly.Location) == "System.Private.CoreLib.dll";
} }
/// <summary> /// <summary>
/// ///
/// We try to manually load assembly. /// We try to manually load assembly.
/// To work test project needs to use /// To work test project needs to use
/// ///
/// <PropertyGroup> /// <PropertyGroup>
/// <PreserveCompilationContext>true</PreserveCompilationContext> /// <PreserveCompilationContext>true</PreserveCompilationContext>
/// </PropertyGroup> /// </PropertyGroup>
/// ///
/// Runtime configuration file doc https://github.com/dotnet/cli/blob/master/Documentation/specs/runtime-configuration-file.md /// Runtime configuration file doc https://github.com/dotnet/cli/blob/master/Documentation/specs/runtime-configuration-file.md
/// ///
/// </summary> /// </summary>
@@ -202,7 +202,7 @@ namespace Coverlet.Core.Instrumentation
catch (Exception ex) catch (Exception ex)
{ {
// if we don't find a lib go on // if we don't find a lib go on
_logger.LogVerbose($"TryWithCustomResolverOnDotNetCore exception: {ex.ToString()}"); _logger.LogVerbose($"TryWithCustomResolverOnDotNetCore exception: {ex}");
} }
} }
} }
@@ -218,8 +218,8 @@ namespace Coverlet.Core.Instrumentation
internal class AspNetCoreSharedFrameworkResolver : ICompilationAssemblyResolver internal class AspNetCoreSharedFrameworkResolver : ICompilationAssemblyResolver
{ {
private readonly string[] _aspNetSharedFrameworkDirs = null; private readonly string[] _aspNetSharedFrameworkDirs;
private readonly ILogger _logger = null; private readonly ILogger _logger;
public AspNetCoreSharedFrameworkResolver(ILogger logger) public AspNetCoreSharedFrameworkResolver(ILogger logger)
{ {
+148 -156
View File
@@ -49,7 +49,7 @@ namespace Coverlet.Core.Instrumentation
private readonly string[] _doesNotReturnAttributes; private readonly string[] _doesNotReturnAttributes;
private ReachabilityHelper _reachabilityHelper; private ReachabilityHelper _reachabilityHelper;
public bool SkipModule { get; set; } = false; public bool SkipModule { get; set; }
public Instrumenter( public Instrumenter(
string module, string module,
@@ -185,158 +185,150 @@ namespace Coverlet.Core.Instrumentation
// locking issues if we do it while writing. // locking issues if we do it while writing.
private void CreateReachabilityHelper() private void CreateReachabilityHelper()
{ {
using (Stream stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.Read)) using Stream stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.Read);
using (var resolver = new NetstandardAwareAssemblyResolver(_module, _logger)) using var resolver = new NetstandardAwareAssemblyResolver(_module, _logger);
resolver.AddSearchDirectory(Path.GetDirectoryName(_module));
var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver };
if (_isCoreLibrary)
{ {
resolver.AddSearchDirectory(Path.GetDirectoryName(_module)); parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider();
var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver };
if (_isCoreLibrary)
{
parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider();
}
using (var module = ModuleDefinition.ReadModule(stream, parameters))
{
_reachabilityHelper = ReachabilityHelper.CreateForModule(module, _doesNotReturnAttributes, _logger);
}
} }
using var module = ModuleDefinition.ReadModule(stream, parameters);
_reachabilityHelper = ReachabilityHelper.CreateForModule(module, _doesNotReturnAttributes, _logger);
} }
private void InstrumentModule() private void InstrumentModule()
{ {
CreateReachabilityHelper(); CreateReachabilityHelper();
using (Stream stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.ReadWrite)) using Stream stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.ReadWrite);
using (var resolver = new NetstandardAwareAssemblyResolver(_module, _logger)) using var resolver = new NetstandardAwareAssemblyResolver(_module, _logger);
resolver.AddSearchDirectory(Path.GetDirectoryName(_module));
var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver };
if (_isCoreLibrary)
{ {
resolver.AddSearchDirectory(Path.GetDirectoryName(_module)); parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider();
var parameters = new ReaderParameters { ReadSymbols = true, AssemblyResolver = resolver }; }
if (_isCoreLibrary)
using var module = ModuleDefinition.ReadModule(stream, parameters);
foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes)
{
if (IsExcludeAttribute(customAttribute))
{ {
parameters.MetadataImporterProvider = new CoreLibMetadataImporterProvider(); _logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute {customAttribute.AttributeType.FullName}");
} SkipModule = true;
return;
using (var module = ModuleDefinition.ReadModule(stream, parameters))
{
foreach (CustomAttribute customAttribute in module.Assembly.CustomAttributes)
{
if (IsExcludeAttribute(customAttribute))
{
_logger.LogVerbose($"Excluded module: '{module}' for assembly level attribute {customAttribute.AttributeType.FullName}");
SkipModule = true;
return;
}
}
bool containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null;
IEnumerable<TypeDefinition> types = module.GetTypes();
AddCustomModuleTrackerToModule(module);
CustomDebugInformation sourceLinkDebugInfo = module.CustomDebugInformations.FirstOrDefault(c => c.Kind == CustomDebugInformationKind.SourceLink);
if (sourceLinkDebugInfo != null)
{
_result.SourceLink = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
}
foreach (TypeDefinition type in types)
{
if (
!Is_System_Threading_Interlocked_CoreLib_Type(type) &&
!IsTypeExcluded(type) &&
_instrumentationHelper.IsTypeIncluded(_module, type.FullName, _parameters.IncludeFilters)
)
{
if (IsSynthesizedMemberToBeExcluded(type))
{
(_excludedCompilerGeneratedTypes ??= new List<string>()).Add(type.FullName);
}
else
{
InstrumentType(type);
}
}
}
// Fixup the custom tracker class constructor, according to all instrumented types
if (_customTrackerRegisterUnloadEventsMethod == null)
{
_customTrackerRegisterUnloadEventsMethod = new MethodReference(
nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef);
}
Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last();
if (!containsAppContext)
{
// For "normal" cases, where the instrumented assembly is not the core library, we add a call to
// RegisterUnloadEvents to the static constructor of the generated custom tracker. Due to static
// initialization constraints, the core library is handled separately below.
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Call, _customTrackerRegisterUnloadEventsMethod));
}
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_parameters.SingleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4_1));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerFlushHitFile));
if (containsAppContext)
{
// Handle the core library by instrumenting System.AppContext.OnProcessExit to directly call
// the UnloadModule method of the custom tracker type. This avoids loops between the static
// initialization of the custom tracker and the static initialization of the hosting AppDomain
// (which for the core library case will be instrumented code).
var eventArgsType = new TypeReference(nameof(System), nameof(EventArgs), module, module.TypeSystem.CoreLibrary);
var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.UnloadModule), module.TypeSystem.Void, _customTrackerTypeDef);
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object));
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(eventArgsType));
var appContextType = new TypeReference(nameof(System), nameof(AppContext), module, module.TypeSystem.CoreLibrary);
MethodDefinition onProcessExitMethod = new MethodReference("OnProcessExit", module.TypeSystem.Void, appContextType).Resolve();
ILProcessor onProcessExitIl = onProcessExitMethod.Body.GetILProcessor();
// Put the OnProcessExit body inside try/finally to ensure the call to the UnloadModule.
Instruction lastInst = onProcessExitMethod.Body.Instructions.Last();
var firstNullParam = Instruction.Create(OpCodes.Ldnull);
var secondNullParam = Instruction.Create(OpCodes.Ldnull);
var callUnload = Instruction.Create(OpCodes.Call, customTrackerUnloadModule);
onProcessExitIl.InsertAfter(lastInst, firstNullParam);
onProcessExitIl.InsertAfter(firstNullParam, secondNullParam);
onProcessExitIl.InsertAfter(secondNullParam, callUnload);
var endFinally = Instruction.Create(OpCodes.Endfinally);
onProcessExitIl.InsertAfter(callUnload, endFinally);
Instruction ret = onProcessExitIl.Create(OpCodes.Ret);
Instruction leaveAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret);
onProcessExitIl.InsertAfter(endFinally, ret);
foreach (Instruction inst in onProcessExitMethod.Body.Instructions.ToArray())
{
// Patch ret to leave after the finally
if (inst.OpCode == OpCodes.Ret && inst != ret)
{
Instruction leaveBodyInstAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret);
Instruction prevInst = inst.Previous;
onProcessExitMethod.Body.Instructions.Remove(inst);
onProcessExitIl.InsertAfter(prevInst, leaveBodyInstAfterFinally);
}
}
var handler = new ExceptionHandler(ExceptionHandlerType.Finally)
{
TryStart = onProcessExitIl.Body.Instructions.First(),
TryEnd = firstNullParam,
HandlerStart = firstNullParam,
HandlerEnd = ret
};
onProcessExitMethod.Body.ExceptionHandlers.Add(handler);
}
module.Write(stream, new WriterParameters { WriteSymbols = true });
} }
} }
bool containsAppContext = module.GetType(nameof(System), nameof(AppContext)) != null;
IEnumerable<TypeDefinition> types = module.GetTypes();
AddCustomModuleTrackerToModule(module);
CustomDebugInformation sourceLinkDebugInfo = module.CustomDebugInformations.FirstOrDefault(c => c.Kind == CustomDebugInformationKind.SourceLink);
if (sourceLinkDebugInfo != null)
{
_result.SourceLink = ((SourceLinkDebugInformation)sourceLinkDebugInfo).Content;
}
foreach (TypeDefinition type in types)
{
if (
!Is_System_Threading_Interlocked_CoreLib_Type(type) &&
!IsTypeExcluded(type) &&
_instrumentationHelper.IsTypeIncluded(_module, type.FullName, _parameters.IncludeFilters)
)
{
if (IsSynthesizedMemberToBeExcluded(type))
{
(_excludedCompilerGeneratedTypes ??= new List<string>()).Add(type.FullName);
}
else
{
InstrumentType(type);
}
}
}
// Fixup the custom tracker class constructor, according to all instrumented types
if (_customTrackerRegisterUnloadEventsMethod == null)
{
_customTrackerRegisterUnloadEventsMethod = new MethodReference(
nameof(ModuleTrackerTemplate.RegisterUnloadEvents), module.TypeSystem.Void, _customTrackerTypeDef);
}
Instruction lastInstr = _customTrackerClassConstructorIl.Body.Instructions.Last();
if (!containsAppContext)
{
// For "normal" cases, where the instrumented assembly is not the core library, we add a call to
// RegisterUnloadEvents to the static constructor of the generated custom tracker. Due to static
// initialization constraints, the core library is handled separately below.
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Call, _customTrackerRegisterUnloadEventsMethod));
}
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4, _result.HitCandidates.Count));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Newarr, module.TypeSystem.Int32));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsArray));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldstr, _result.HitsFilePath));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerHitsFilePath));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(_parameters.SingleHit ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerSingleHit));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Ldc_I4_1));
_customTrackerClassConstructorIl.InsertBefore(lastInstr, Instruction.Create(OpCodes.Stsfld, _customTrackerFlushHitFile));
if (containsAppContext)
{
// Handle the core library by instrumenting System.AppContext.OnProcessExit to directly call
// the UnloadModule method of the custom tracker type. This avoids loops between the static
// initialization of the custom tracker and the static initialization of the hosting AppDomain
// (which for the core library case will be instrumented code).
var eventArgsType = new TypeReference(nameof(System), nameof(EventArgs), module, module.TypeSystem.CoreLibrary);
var customTrackerUnloadModule = new MethodReference(nameof(ModuleTrackerTemplate.UnloadModule), module.TypeSystem.Void, _customTrackerTypeDef);
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(module.TypeSystem.Object));
customTrackerUnloadModule.Parameters.Add(new ParameterDefinition(eventArgsType));
var appContextType = new TypeReference(nameof(System), nameof(AppContext), module, module.TypeSystem.CoreLibrary);
MethodDefinition onProcessExitMethod = new MethodReference("OnProcessExit", module.TypeSystem.Void, appContextType).Resolve();
ILProcessor onProcessExitIl = onProcessExitMethod.Body.GetILProcessor();
// Put the OnProcessExit body inside try/finally to ensure the call to the UnloadModule.
Instruction lastInst = onProcessExitMethod.Body.Instructions.Last();
var firstNullParam = Instruction.Create(OpCodes.Ldnull);
var secondNullParam = Instruction.Create(OpCodes.Ldnull);
var callUnload = Instruction.Create(OpCodes.Call, customTrackerUnloadModule);
onProcessExitIl.InsertAfter(lastInst, firstNullParam);
onProcessExitIl.InsertAfter(firstNullParam, secondNullParam);
onProcessExitIl.InsertAfter(secondNullParam, callUnload);
var endFinally = Instruction.Create(OpCodes.Endfinally);
onProcessExitIl.InsertAfter(callUnload, endFinally);
Instruction ret = onProcessExitIl.Create(OpCodes.Ret);
Instruction leaveAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret);
onProcessExitIl.InsertAfter(endFinally, ret);
foreach (Instruction inst in onProcessExitMethod.Body.Instructions.ToArray())
{
// Patch ret to leave after the finally
if (inst.OpCode == OpCodes.Ret && inst != ret)
{
Instruction leaveBodyInstAfterFinally = onProcessExitIl.Create(OpCodes.Leave, ret);
Instruction prevInst = inst.Previous;
onProcessExitMethod.Body.Instructions.Remove(inst);
onProcessExitIl.InsertAfter(prevInst, leaveBodyInstAfterFinally);
}
}
var handler = new ExceptionHandler(ExceptionHandlerType.Finally)
{
TryStart = onProcessExitIl.Body.Instructions.First(),
TryEnd = firstNullParam,
HandlerStart = firstNullParam,
HandlerEnd = ret
};
onProcessExitMethod.Body.ExceptionHandlers.Add(handler);
}
module.Write(stream, new WriterParameters { WriteSymbols = true });
} }
private void AddCustomModuleTrackerToModule(ModuleDefinition module) private void AddCustomModuleTrackerToModule(ModuleDefinition module)
@@ -864,52 +856,52 @@ namespace Coverlet.Core.Instrumentation
private class CoreLibMetadataImporter : IMetadataImporter private class CoreLibMetadataImporter : IMetadataImporter
{ {
private readonly ModuleDefinition module; private readonly ModuleDefinition _module;
private readonly DefaultMetadataImporter defaultMetadataImporter; private readonly DefaultMetadataImporter _defaultMetadataImporter;
public CoreLibMetadataImporter(ModuleDefinition module) public CoreLibMetadataImporter(ModuleDefinition module)
{ {
this.module = module; _module = module;
defaultMetadataImporter = new DefaultMetadataImporter(module); _defaultMetadataImporter = new DefaultMetadataImporter(module);
} }
public AssemblyNameReference ImportReference(AssemblyNameReference reference) public AssemblyNameReference ImportReference(AssemblyNameReference reference)
{ {
return defaultMetadataImporter.ImportReference(reference); return _defaultMetadataImporter.ImportReference(reference);
} }
public TypeReference ImportReference(TypeReference type, IGenericParameterProvider context) public TypeReference ImportReference(TypeReference type, IGenericParameterProvider context)
{ {
TypeReference importedRef = defaultMetadataImporter.ImportReference(type, context); TypeReference importedRef = _defaultMetadataImporter.ImportReference(type, context);
importedRef.GetElementType().Scope = module.TypeSystem.CoreLibrary; importedRef.GetElementType().Scope = _module.TypeSystem.CoreLibrary;
return importedRef; return importedRef;
} }
public FieldReference ImportReference(FieldReference field, IGenericParameterProvider context) public FieldReference ImportReference(FieldReference field, IGenericParameterProvider context)
{ {
FieldReference importedRef = defaultMetadataImporter.ImportReference(field, context); FieldReference importedRef = _defaultMetadataImporter.ImportReference(field, context);
importedRef.FieldType.GetElementType().Scope = module.TypeSystem.CoreLibrary; importedRef.FieldType.GetElementType().Scope = _module.TypeSystem.CoreLibrary;
return importedRef; return importedRef;
} }
public MethodReference ImportReference(MethodReference method, IGenericParameterProvider context) public MethodReference ImportReference(MethodReference method, IGenericParameterProvider context)
{ {
MethodReference importedRef = defaultMetadataImporter.ImportReference(method, context); MethodReference importedRef = _defaultMetadataImporter.ImportReference(method, context);
importedRef.DeclaringType.GetElementType().Scope = module.TypeSystem.CoreLibrary; importedRef.DeclaringType.GetElementType().Scope = _module.TypeSystem.CoreLibrary;
foreach (ParameterDefinition parameter in importedRef.Parameters) foreach (ParameterDefinition parameter in importedRef.Parameters)
{ {
if (parameter.ParameterType.Scope == module.TypeSystem.CoreLibrary) if (parameter.ParameterType.Scope == _module.TypeSystem.CoreLibrary)
{ {
continue; continue;
} }
parameter.ParameterType.GetElementType().Scope = module.TypeSystem.CoreLibrary; parameter.ParameterType.GetElementType().Scope = _module.TypeSystem.CoreLibrary;
} }
if (importedRef.ReturnType.Scope != module.TypeSystem.CoreLibrary) if (importedRef.ReturnType.Scope != _module.TypeSystem.CoreLibrary)
{ {
importedRef.ReturnType.GetElementType().Scope = module.TypeSystem.CoreLibrary; importedRef.ReturnType.GetElementType().Scope = _module.TypeSystem.CoreLibrary;
} }
return importedRef; return importedRef;
@@ -26,8 +26,8 @@ namespace Coverlet.Core.Instrumentation
public static int[] HitsArray; public static int[] HitsArray;
public static bool SingleHit; public static bool SingleHit;
public static bool FlushHitFile; public static bool FlushHitFile;
private static readonly bool _enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) ? result == 1 : false; private static readonly bool s_enableLog = int.TryParse(Environment.GetEnvironmentVariable("COVERLET_ENABLETRACKERLOG"), out int result) && result == 1;
private static readonly string _sessionId = Guid.NewGuid().ToString(); private static readonly string s_sessionId = Guid.NewGuid().ToString();
static ModuleTrackerTemplate() static ModuleTrackerTemplate()
{ {
@@ -83,100 +83,94 @@ namespace Coverlet.Core.Instrumentation
{ {
// The same module can be unloaded multiple times in the same process via different app domains. // The same module can be unloaded multiple times in the same process via different app domains.
// Use a global mutex to ensure no concurrent access. // Use a global mutex to ensure no concurrent access.
using (var mutex = new Mutex(true, Path.GetFileNameWithoutExtension(HitsFilePath) + "_Mutex", out bool createdNew)) using var mutex = new Mutex(true, Path.GetFileNameWithoutExtension(HitsFilePath) + "_Mutex", out bool createdNew);
if (!createdNew)
{ {
if (!createdNew) mutex.WaitOne();
{ }
mutex.WaitOne();
}
if (FlushHitFile) if (FlushHitFile)
{
try
{ {
// Claim the current hits array and reset it to prevent double-counting scenarios.
int[] hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]);
WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}' by '{sender ?? "null"}'");
WriteLog($"Flushing hit file '{HitsFilePath}'");
bool failedToCreateNewHitsFile = false;
try try
{ {
// Claim the current hits array and reset it to prevent double-counting scenarios. using var fs = new FileStream(HitsFilePath, FileMode.CreateNew);
int[] hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]); using var bw = new BinaryWriter(fs);
bw.Write(hitsArray.Length);
WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}' by '{sender ?? "null"}'"); foreach (int hitCount in hitsArray)
WriteLog($"Flushing hit file '{HitsFilePath}'");
bool failedToCreateNewHitsFile = false;
try
{ {
using (var fs = new FileStream(HitsFilePath, FileMode.CreateNew)) bw.Write(hitCount);
using (var bw = new BinaryWriter(fs))
{
bw.Write(hitsArray.Length);
foreach (int hitCount in hitsArray)
{
bw.Write(hitCount);
}
}
} }
catch (Exception ex)
{
WriteLog($"Failed to create new hits file '{HitsFilePath}' -> '{ex.Message}'");
failedToCreateNewHitsFile = true;
}
if (failedToCreateNewHitsFile)
{
// Update the number of hits by adding value on disk with the ones on memory.
// This path should be triggered only in the case of multiple AppDomain unloads.
using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
using (var br = new BinaryReader(fs))
using (var bw = new BinaryWriter(fs))
{
int hitsLength = br.ReadInt32();
WriteLog($"Current hits found '{hitsLength}'");
if (hitsLength != hitsArray.Length)
{
throw new InvalidOperationException($"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {hitsArray.Length}");
}
for (int i = 0; i < hitsLength; ++i)
{
int oldHitCount = br.ReadInt32();
bw.Seek(-sizeof(int), SeekOrigin.Current);
if (SingleHit)
{
bw.Write(hitsArray[i] + oldHitCount > 0 ? 1 : 0);
}
else
{
bw.Write(hitsArray[i] + oldHitCount);
}
}
}
}
WriteHits(sender);
WriteLog($"Hit file '{HitsFilePath}' flushed, size {new FileInfo(HitsFilePath).Length}");
WriteLog("--------------------------------");
} }
catch (Exception ex) catch (Exception ex)
{ {
WriteLog(ex.ToString()); WriteLog($"Failed to create new hits file '{HitsFilePath}' -> '{ex.Message}'");
throw; failedToCreateNewHitsFile = true;
} }
}
// On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file if (failedToCreateNewHitsFile)
// this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll. {
mutex.ReleaseMutex(); // Update the number of hits by adding value on disk with the ones on memory.
// This path should be triggered only in the case of multiple AppDomain unloads.
using var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
using var br = new BinaryReader(fs);
using var bw = new BinaryWriter(fs);
int hitsLength = br.ReadInt32();
WriteLog($"Current hits found '{hitsLength}'");
if (hitsLength != hitsArray.Length)
{
throw new InvalidOperationException($"{HitsFilePath} has {hitsLength} entries but on memory {nameof(HitsArray)} has {hitsArray.Length}");
}
for (int i = 0; i < hitsLength; ++i)
{
int oldHitCount = br.ReadInt32();
bw.Seek(-sizeof(int), SeekOrigin.Current);
if (SingleHit)
{
bw.Write(hitsArray[i] + oldHitCount > 0 ? 1 : 0);
}
else
{
bw.Write(hitsArray[i] + oldHitCount);
}
}
}
WriteHits(sender);
WriteLog($"Hit file '{HitsFilePath}' flushed, size {new FileInfo(HitsFilePath).Length}");
WriteLog("--------------------------------");
}
catch (Exception ex)
{
WriteLog(ex.ToString());
throw;
}
} }
// On purpose this is not under a try-finally: it is better to have an exception if there was any error writing the hits file
// this case is relevant when instrumenting corelib since multiple processes can be running against the same instrumented dll.
mutex.ReleaseMutex();
} }
private static void WriteHits(object sender) private static void WriteHits(object sender)
{ {
if (_enableLog) if (s_enableLog)
{ {
var currentAssembly = Assembly.GetExecutingAssembly(); var currentAssembly = Assembly.GetExecutingAssembly();
var location = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(currentAssembly.Location), "TrackersHitsLog")); var location = new DirectoryInfo(Path.Combine(Path.GetDirectoryName(currentAssembly.Location), "TrackersHitsLog"));
location.Create(); location.Create();
string logFile = Path.Combine(location.FullName, $"{Path.GetFileName(currentAssembly.Location)}_{DateTime.UtcNow.Ticks}_{_sessionId}.txt"); string logFile = Path.Combine(location.FullName, $"{Path.GetFileName(currentAssembly.Location)}_{DateTime.UtcNow.Ticks}_{s_sessionId}.txt");
using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) using (var fs = new FileStream(HitsFilePath, FileMode.Open, FileAccess.ReadWrite, FileShare.None))
using (var log = new FileStream(logFile, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None)) using (var log = new FileStream(logFile, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None))
using (var logWriter = new StreamWriter(log)) using (var logWriter = new StreamWriter(log))
@@ -195,12 +189,12 @@ namespace Coverlet.Core.Instrumentation
private static void WriteLog(string logText) private static void WriteLog(string logText)
{ {
if (_enableLog) if (s_enableLog)
{ {
// We don't set path as global var to keep benign possible errors inside try/catch // We don't set path as global var to keep benign possible errors inside try/catch
// I'm not sure that location will be ok in every scenario // I'm not sure that location will be ok in every scenario
string location = Assembly.GetExecutingAssembly().Location; string location = Assembly.GetExecutingAssembly().Location;
File.AppendAllText(Path.Combine(Path.GetDirectoryName(location), Path.GetFileName(location) + "_tracker.txt"), $"[{DateTime.UtcNow} S:{_sessionId} T:{Thread.CurrentThread.ManagedThreadId}]{logText}{Environment.NewLine}"); File.AppendAllText(Path.Combine(Path.GetDirectoryName(location), Path.GetFileName(location) + "_tracker.txt"), $"[{DateTime.UtcNow} S:{s_sessionId} T:{Thread.CurrentThread.ManagedThreadId}]{logText}{Environment.NewLine}");
} }
} }
} }
@@ -100,9 +100,9 @@ namespace Coverlet.Core.Instrumentation.Reachability
/// <summary> /// <summary>
/// Returns true if this branch has multiple targets. /// Returns true if this branch has multiple targets.
/// </summary> /// </summary>
public bool HasMultiTargets => _TargetOffset == -1; public bool HasMultiTargets => _targetOffset == -1;
private readonly int _TargetOffset; private readonly int _targetOffset;
/// <summary> /// <summary>
/// Target of the branch, assuming it has a single target. /// Target of the branch, assuming it has a single target.
@@ -118,11 +118,11 @@ namespace Coverlet.Core.Instrumentation.Reachability
throw new InvalidOperationException($"{HasMultiTargets} is true"); throw new InvalidOperationException($"{HasMultiTargets} is true");
} }
return _TargetOffset; return _targetOffset;
} }
} }
private readonly ImmutableArray<int> _TargetOffsets; private readonly ImmutableArray<int> _targetOffsets;
/// <summary> /// <summary>
/// Targets of the branch, assuming it has multiple targets. /// Targets of the branch, assuming it has multiple targets.
@@ -138,15 +138,15 @@ namespace Coverlet.Core.Instrumentation.Reachability
throw new InvalidOperationException($"{HasMultiTargets} is false"); throw new InvalidOperationException($"{HasMultiTargets} is false");
} }
return _TargetOffsets; return _targetOffsets;
} }
} }
public BranchInstruction(int offset, int targetOffset) public BranchInstruction(int offset, int targetOffset)
{ {
Offset = offset; Offset = offset;
_TargetOffset = targetOffset; _targetOffset = targetOffset;
_TargetOffsets = ImmutableArray<int>.Empty; _targetOffsets = ImmutableArray<int>.Empty;
} }
public BranchInstruction(int offset, ImmutableArray<int> targetOffset) public BranchInstruction(int offset, ImmutableArray<int> targetOffset)
@@ -157,8 +157,8 @@ namespace Coverlet.Core.Instrumentation.Reachability
} }
Offset = offset; Offset = offset;
_TargetOffset = -1; _targetOffset = -1;
_TargetOffsets = targetOffset; _targetOffsets = targetOffset;
} }
public override string ToString() public override string ToString()
@@ -169,7 +169,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
/// OpCodes that transfer control code, even if they do not /// OpCodes that transfer control code, even if they do not
/// introduce branch points. /// introduce branch points.
/// </summary> /// </summary>
private static readonly ImmutableHashSet<OpCode> BRANCH_OPCODES = private static readonly ImmutableHashSet<OpCode> s_branchOpCodes =
ImmutableHashSet.CreateRange( ImmutableHashSet.CreateRange(
new[] new[]
{ {
@@ -224,7 +224,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
/// OpCodes that unconditionally transfer control, so there /// OpCodes that unconditionally transfer control, so there
/// is not "fall through" branch target. /// is not "fall through" branch target.
/// </summary> /// </summary>
private static readonly ImmutableHashSet<OpCode> UNCONDITIONAL_BRANCH_OPCODES = private static readonly ImmutableHashSet<OpCode> s_unconditionalBranchOpCodes =
ImmutableHashSet.CreateRange( ImmutableHashSet.CreateRange(
new[] new[]
{ {
@@ -235,11 +235,11 @@ namespace Coverlet.Core.Instrumentation.Reachability
} }
); );
private readonly ImmutableHashSet<MetadataToken> DoesNotReturnMethods; private readonly ImmutableHashSet<MetadataToken> _doesNotReturnMethods;
private ReachabilityHelper(ImmutableHashSet<MetadataToken> doesNotReturnMethods) private ReachabilityHelper(ImmutableHashSet<MetadataToken> doesNotReturnMethods)
{ {
DoesNotReturnMethods = doesNotReturnMethods; _doesNotReturnMethods = doesNotReturnMethods;
} }
/// <summary> /// <summary>
@@ -372,7 +372,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
} }
// no known methods that do not return, so everything is reachable by definition // no known methods that do not return, so everything is reachable by definition
if (DoesNotReturnMethods.IsEmpty) if (_doesNotReturnMethods.IsEmpty)
{ {
return ImmutableArray<UnreachableRange>.Empty; return ImmutableArray<UnreachableRange>.Empty;
} }
@@ -406,7 +406,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
{ {
containsDoesNotReturnCall = containsDoesNotReturnCall || DoesNotReturn(i); containsDoesNotReturnCall = containsDoesNotReturnCall || DoesNotReturn(i);
if (BRANCH_OPCODES.Contains(i.OpCode)) if (s_branchOpCodes.Contains(i.OpCode))
{ {
(int? singleTargetOffset, ImmutableArray<int> multiTargetOffsets) = GetInstructionTargets(i, exceptionHandlers); (int? singleTargetOffset, ImmutableArray<int> multiTargetOffsets) = GetInstructionTargets(i, exceptionHandlers);
@@ -453,7 +453,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
{ {
// it's any of the B.*(_S)? or Leave(_S)? instructions // it's any of the B.*(_S)? or Leave(_S)? instructions
if (UNCONDITIONAL_BRANCH_OPCODES.Contains(i.OpCode)) if (s_unconditionalBranchOpCodes.Contains(i.OpCode))
{ {
multiTargetOffsets = ImmutableArray<int>.Empty; multiTargetOffsets = ImmutableArray<int>.Empty;
singleTargetOffset = targetInstr.Offset; singleTargetOffset = targetInstr.Offset;
@@ -527,7 +527,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
/// <summary> /// <summary>
/// Calculates which ranges of IL are unreachable, given blocks which have head and tail reachability calculated. /// Calculates which ranges of IL are unreachable, given blocks which have head and tail reachability calculated.
/// </summary> /// </summary>
private ImmutableArray<UnreachableRange> DetermineUnreachableRanges(ImmutableArray<BasicBlock> blocks, int lastInstructionOffset) private static ImmutableArray<UnreachableRange> DetermineUnreachableRanges(ImmutableArray<BasicBlock> blocks, int lastInstructionOffset)
{ {
ImmutableArray<UnreachableRange>.Builder ret = ImmutableArray.CreateBuilder<UnreachableRange>(); ImmutableArray<UnreachableRange>.Builder ret = ImmutableArray.CreateBuilder<UnreachableRange>();
@@ -580,7 +580,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
/// ///
/// "Tail reachability" will have already been determined in CreateBlocks. /// "Tail reachability" will have already been determined in CreateBlocks.
/// </summary> /// </summary>
private void DetermineHeadReachability(ImmutableArray<BasicBlock> blocks) private static void DetermineHeadReachability(ImmutableArray<BasicBlock> blocks)
{ {
var blockLookup = blocks.ToImmutableDictionary(b => b.StartOffset); var blockLookup = blocks.ToImmutableDictionary(b => b.StartOffset);
@@ -791,7 +791,7 @@ namespace Coverlet.Core.Instrumentation.Reachability
return false; return false;
} }
return DoesNotReturnMethods.Contains(mtd.MetadataToken); return _doesNotReturnMethods.Contains(mtd.MetadataToken);
} }
/// <summary> /// <summary>
@@ -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. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System; using System;
@@ -111,7 +111,7 @@ namespace Coverlet.Core.Reporters
{ {
var condition = new XElement("condition"); var condition = new XElement("condition");
condition.Add(new XAttribute("number", entry.Key)); 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("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", $"{summary.CalculateBranchCoverage(entry.Value).Percent.ToString(CultureInfo.InvariantCulture)}%"));
conditions.Add(condition); conditions.Add(condition);
} }
@@ -119,7 +119,6 @@ namespace Coverlet.Core.Reporters
line.Add(conditions); line.Add(conditions);
} }
lines.Add(line); lines.Add(line);
classLines.Add(line); classLines.Add(line);
} }
@@ -234,4 +233,4 @@ namespace Coverlet.Core.Reporters
return path; return path;
} }
} }
} }
@@ -39,7 +39,7 @@ namespace Coverlet.Core.Reporters
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
private void OutputLineCoverage(CoverageDetails coverageDetails, StringBuilder builder) private static void OutputLineCoverage(CoverageDetails coverageDetails, StringBuilder builder)
{ {
// The number of covered lines // The number of covered lines
OutputTeamCityServiceMessage("CodeCoverageAbsLCovered", coverageDetails.Covered, builder); OutputTeamCityServiceMessage("CodeCoverageAbsLCovered", coverageDetails.Covered, builder);
@@ -48,7 +48,7 @@ namespace Coverlet.Core.Reporters
OutputTeamCityServiceMessage("CodeCoverageAbsLTotal", coverageDetails.Total, builder); OutputTeamCityServiceMessage("CodeCoverageAbsLTotal", coverageDetails.Total, builder);
} }
private void OutputBranchCoverage(CoverageDetails coverageDetails, StringBuilder builder) private static void OutputBranchCoverage(CoverageDetails coverageDetails, StringBuilder builder)
{ {
// The number of covered branches // The number of covered branches
OutputTeamCityServiceMessage("CodeCoverageAbsBCovered", coverageDetails.Covered, builder); OutputTeamCityServiceMessage("CodeCoverageAbsBCovered", coverageDetails.Covered, builder);
@@ -57,7 +57,7 @@ namespace Coverlet.Core.Reporters
OutputTeamCityServiceMessage("CodeCoverageAbsBTotal", coverageDetails.Total, builder); OutputTeamCityServiceMessage("CodeCoverageAbsBTotal", coverageDetails.Total, builder);
} }
private void OutputMethodCoverage(CoverageDetails coverageDetails, StringBuilder builder) private static void OutputMethodCoverage(CoverageDetails coverageDetails, StringBuilder builder)
{ {
// The number of covered methods // The number of covered methods
OutputTeamCityServiceMessage("CodeCoverageAbsMCovered", coverageDetails.Covered, builder); OutputTeamCityServiceMessage("CodeCoverageAbsMCovered", coverageDetails.Covered, builder);
@@ -66,7 +66,7 @@ namespace Coverlet.Core.Reporters
OutputTeamCityServiceMessage("CodeCoverageAbsMTotal", coverageDetails.Total, builder); OutputTeamCityServiceMessage("CodeCoverageAbsMTotal", coverageDetails.Total, builder);
} }
private void OutputTeamCityServiceMessage(string key, double value, StringBuilder builder) private static void OutputTeamCityServiceMessage(string key, double value, StringBuilder builder)
{ {
builder.AppendLine($"##teamcity[buildStatisticValue key='{key}' value='{value.ToString("0.##", new CultureInfo("en-US"))}']"); builder.AppendLine($"##teamcity[buildStatisticValue key='{key}' value='{value.ToString("0.##", new CultureInfo("en-US"))}']");
} }
+5 -11
View File
@@ -20,8 +20,8 @@ namespace Coverlet.Core.Symbols
{ {
private const int StepOverLineCode = 0xFEEFEE; private const int StepOverLineCode = 0xFEEFEE;
// Create single instance, we cannot collide because we use full method name as key // Create single instance, we cannot collide because we use full method name as key
private readonly ConcurrentDictionary<string, int[]> _compilerGeneratedBranchesToExclude = new ConcurrentDictionary<string, int[]>(); private readonly ConcurrentDictionary<string, int[]> _compilerGeneratedBranchesToExclude = new();
private readonly ConcurrentDictionary<string, List<int>> _sequencePointOffsetToSkip = new ConcurrentDictionary<string, List<int>>(); private readonly ConcurrentDictionary<string, List<int>> _sequencePointOffsetToSkip = new();
// In case of nested compiler generated classes, only the root one presents the CompilerGenerated attribute. // In case of nested compiler generated classes, only the root one presents the CompilerGenerated attribute.
// So let's search up to the outermost declaring type to find the attribute // So let's search up to the outermost declaring type to find the attribute
@@ -83,7 +83,8 @@ namespace Coverlet.Core.Symbols
{ {
return false; return false;
} }
if (methodDefinition.DeclaringType.CustomAttributes.Count(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName) > 0)
if (methodDefinition.DeclaringType.CustomAttributes.Any(ca => ca.AttributeType.FullName == typeof(CompilerGeneratedAttribute).FullName))
{ {
foreach (InterfaceImplementation implementedInterface in methodDefinition.DeclaringType.Interfaces) foreach (InterfaceImplementation implementedInterface in methodDefinition.DeclaringType.Interfaces)
{ {
@@ -465,7 +466,6 @@ namespace Coverlet.Core.Symbols
CheckIfExceptionThrown(instructions, instruction, currentIndex) || CheckIfExceptionThrown(instructions, instruction, currentIndex) ||
CheckThrownExceptionType(instructions, instruction, currentIndex); CheckThrownExceptionType(instructions, instruction, currentIndex);
// The pattern for the "should we stay in the loop or not?", which we don't // The pattern for the "should we stay in the loop or not?", which we don't
// want to skip (so we have no method to try to find it), looks like this: // want to skip (so we have no method to try to find it), looks like this:
// //
@@ -477,7 +477,6 @@ namespace Coverlet.Core.Symbols
// the "call" and branch, but it's the same idea either way: branch // the "call" and branch, but it's the same idea either way: branch
// if GetResult() returned true. // if GetResult() returned true.
static bool CheckForAsyncEnumerator(List<Instruction> instructions, Instruction instruction, int currentIndex) static bool CheckForAsyncEnumerator(List<Instruction> instructions, Instruction instruction, int currentIndex)
{ {
// We're looking for the following pattern, which checks whether a // We're looking for the following pattern, which checks whether a
@@ -506,7 +505,6 @@ namespace Coverlet.Core.Symbols
return false; return false;
} }
static bool CheckIfExceptionThrown(List<Instruction> instructions, Instruction instruction, int currentIndex) static bool CheckIfExceptionThrown(List<Instruction> instructions, Instruction instruction, int currentIndex)
{ {
// Here, we want to find a pattern where we're checking whether a // Here, we want to find a pattern where we're checking whether a
@@ -564,7 +562,6 @@ namespace Coverlet.Core.Symbols
return false; return false;
} }
static bool CheckThrownExceptionType(List<Instruction> instructions, Instruction instruction, int currentIndex) static bool CheckThrownExceptionType(List<Instruction> instructions, Instruction instruction, int currentIndex)
{ {
// In this case, we're looking for a branch generated by the compiler to // In this case, we're looking for a branch generated by the compiler to
@@ -617,7 +614,6 @@ namespace Coverlet.Core.Symbols
return CheckForSkipDisposal(instructions, instruction, currentIndex) || return CheckForSkipDisposal(instructions, instruction, currentIndex) ||
CheckForCleanup(instructions, instruction, currentIndex); CheckForCleanup(instructions, instruction, currentIndex);
static bool CheckForSkipDisposal(List<Instruction> instructions, Instruction instruction, int currentIndex) static bool CheckForSkipDisposal(List<Instruction> instructions, Instruction instruction, int currentIndex)
{ {
// The async state machine generated for an "await using" contains a branch // The async state machine generated for an "await using" contains a branch
@@ -727,7 +723,6 @@ namespace Coverlet.Core.Symbols
return false; return false;
} }
static bool CheckForCleanup(List<Instruction> instructions, Instruction instruction, int currentIndex) static bool CheckForCleanup(List<Instruction> instructions, Instruction instruction, int currentIndex)
{ {
// The pattern we're looking for here is this: // The pattern we're looking for here is this:
@@ -812,7 +807,6 @@ namespace Coverlet.Core.Symbols
return CheckForStateSwitch(instructions, instruction, currentIndex) || return CheckForStateSwitch(instructions, instruction, currentIndex) ||
DisposeCheck(instructions, instruction, currentIndex); DisposeCheck(instructions, instruction, currentIndex);
static bool CheckForStateSwitch(List<Instruction> instructions, Instruction instruction, int currentIndex) static bool CheckForStateSwitch(List<Instruction> instructions, Instruction instruction, int currentIndex)
{ {
// The pattern we're looking for here is this one: // The pattern we're looking for here is this one:
@@ -890,7 +884,7 @@ namespace Coverlet.Core.Symbols
} }
} }
private bool SkipGeneratedBranchesForEnumeratorCancellationAttribute(List<Instruction> instructions, Instruction instruction) private static bool SkipGeneratedBranchesForEnumeratorCancellationAttribute(List<Instruction> instructions, Instruction instruction)
{ {
// For async-enumerable methods an additional cancellation token despite the default one can be passed. // For async-enumerable methods an additional cancellation token despite the default one can be passed.
// The EnumeratorCancellation attribute marks the parameter whose value is received by GetAsyncEnumerator(CancellationToken). // The EnumeratorCancellation attribute marks the parameter whose value is received by GetAsyncEnumerator(CancellationToken).
@@ -152,9 +152,9 @@ namespace Coverlet.MSbuild.Tasks
if (Threshold.Contains(',')) if (Threshold.Contains(','))
{ {
IEnumerable<string> thresholdValues = Threshold.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); IEnumerable<string> thresholdValues = Threshold.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim());
if (thresholdValues.Count() != thresholdTypeFlagQueue.Count()) if (thresholdValues.Count() != thresholdTypeFlagQueue.Count)
{ {
throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count()}) and values count ({thresholdValues.Count()}) doesn't match"); throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match");
} }
foreach (string threshold in thresholdValues) foreach (string threshold in thresholdValues)
@@ -112,13 +112,9 @@ namespace Coverlet.MSbuild.Tasks
CoveragePrepareResult prepareResult = coverage.PrepareModules(); CoveragePrepareResult prepareResult = coverage.PrepareModules();
InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName());
using (Stream instrumentedStateFile = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write)) using Stream instrumentedStateFile = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write);
{ using Stream serializedState = CoveragePrepareResult.Serialize(prepareResult);
using (Stream serializedState = CoveragePrepareResult.Serialize(prepareResult)) serializedState.CopyTo(instrumentedStateFile);
{
serializedState.CopyTo(instrumentedStateFile);
}
}
} }
catch (Exception ex) catch (Exception ex)
{ {
@@ -188,14 +188,14 @@ namespace Coverlet.Collector.Tests
Assert.Equal(defaultFormat, coverletSettings.ReportFormats[0]); Assert.Equal(defaultFormat, coverletSettings.ReportFormats[0]);
} }
private void CreateCoverletNodes(XmlDocument doc, XmlElement configElement, string nodeSetting, string nodeValue) private static void CreateCoverletNodes(XmlDocument doc, XmlElement configElement, string nodeSetting, string nodeValue)
{ {
XmlNode node = doc.CreateNode("element", nodeSetting, string.Empty); XmlNode node = doc.CreateNode("element", nodeSetting, string.Empty);
node.InnerText = nodeValue; node.InnerText = nodeValue;
configElement.AppendChild(node); configElement.AppendChild(node);
} }
private void CreateCoverletNullInnerTextNodes(XmlDocument doc, XmlElement configElement, string nodeSetting) private static void CreateCoverletNullInnerTextNodes(XmlDocument doc, XmlElement configElement, string nodeSetting)
{ {
XmlNode node = doc.CreateNode("element", nodeSetting, string.Empty); XmlNode node = doc.CreateNode("element", nodeSetting, string.Empty);
node.InnerText = null; node.InnerText = null;
@@ -13,7 +13,7 @@ namespace Coverlet.Core.Tests
{ {
public partial class CoverageTests public partial class CoverageTests
{ {
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>(); private readonly Mock<ILogger> _mockLogger = new();
[Fact] [Fact]
public void TestCoverage() public void TestCoverage()
@@ -23,7 +23,7 @@ namespace Coverlet.Core.Tests
{ {
static class TestInstrumentationHelper static class TestInstrumentationHelper
{ {
private static IServiceProvider _processWideContainer; private static IServiceProvider s_processWideContainer;
/// <summary> /// <summary>
/// caller sample: TestInstrumentationHelper.GenerateHtmlReport(result, sourceFileFilter: @"+**\Samples\Instrumentation.cs"); /// caller sample: TestInstrumentationHelper.GenerateHtmlReport(result, sourceFileFilter: @"+**\Samples\Instrumentation.cs");
@@ -64,9 +64,9 @@ namespace Coverlet.Core.Tests
{ {
Assert.DoesNotContain("not found for module: ", message); Assert.DoesNotContain("not found for module: ", message);
}); });
_processWideContainer.GetRequiredService<IInstrumentationHelper>().SetLogger(logger.Object); s_processWideContainer.GetRequiredService<IInstrumentationHelper>().SetLogger(logger.Object);
var coveragePrepareResultLoaded = CoveragePrepareResult.Deserialize(result); var coveragePrepareResultLoaded = CoveragePrepareResult.Deserialize(result);
var coverage = new Coverage(coveragePrepareResultLoaded, logger.Object, _processWideContainer.GetService<IInstrumentationHelper>(), new FileSystem(), new SourceRootTranslator(new Mock<ILogger>().Object, new FileSystem())); var coverage = new Coverage(coveragePrepareResultLoaded, logger.Object, s_processWideContainer.GetService<IInstrumentationHelper>(), new FileSystem(), new SourceRootTranslator(new Mock<ILogger>().Object, new FileSystem()));
return coverage.GetCoverageResult(); return coverage.GetCoverageResult();
} }
@@ -121,7 +121,7 @@ namespace Coverlet.Core.Tests
// Instrument module // Instrument module
var coverage = new Coverage(newPath, parameters, new Logger(logFile), var coverage = new Coverage(newPath, parameters, new Logger(logFile),
_processWideContainer.GetService<IInstrumentationHelper>(), _processWideContainer.GetService<IFileSystem>(), _processWideContainer.GetService<ISourceRootTranslator>(), _processWideContainer.GetService<ICecilSymbolHelper>()); s_processWideContainer.GetService<IInstrumentationHelper>(), s_processWideContainer.GetService<IFileSystem>(), s_processWideContainer.GetService<ISourceRootTranslator>(), s_processWideContainer.GetService<ICecilSymbolHelper>());
CoveragePrepareResult prepareResult = coverage.PrepareModules(); CoveragePrepareResult prepareResult = coverage.PrepareModules();
Assert.Single(prepareResult.Results); Assert.Single(prepareResult.Results);
@@ -153,7 +153,7 @@ namespace Coverlet.Core.Tests
private static void SetTestContainer(string testModule = null, bool disableRestoreModules = false) private static void SetTestContainer(string testModule = null, bool disableRestoreModules = false)
{ {
LazyInitializer.EnsureInitialized(ref _processWideContainer, () => LazyInitializer.EnsureInitialized(ref s_processWideContainer, () =>
{ {
var serviceCollection = new ServiceCollection(); var serviceCollection = new ServiceCollection();
serviceCollection.AddTransient<IRetryHelper, CustomRetryHelper>(); serviceCollection.AddTransient<IRetryHelper, CustomRetryHelper>();
@@ -291,7 +291,7 @@ namespace Coverlet.Core.Tests
public abstract class ExternalProcessExecutionTest public abstract class ExternalProcessExecutionTest
{ {
protected FunctionExecutor FunctionExecutor = new FunctionExecutor( protected FunctionExecutor FunctionExecutor = new(
o => o =>
{ {
o.StartInfo.RedirectStandardError = true; o.StartInfo.RedirectStandardError = true;
@@ -15,7 +15,7 @@ namespace Coverlet.Core.Helpers.Tests
public class InstrumentationHelperTests public class InstrumentationHelperTests
{ {
private readonly InstrumentationHelper _instrumentationHelper = private readonly InstrumentationHelper _instrumentationHelper =
new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock<ILogger>().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock<ILogger>().Object, new FileSystem())); new(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock<ILogger>().Object, new SourceRootTranslator(typeof(InstrumentationHelperTests).Assembly.Location, new Mock<ILogger>().Object, new FileSystem()));
[Fact] [Fact]
public void TestGetDependencies() public void TestGetDependencies()
@@ -26,12 +26,12 @@ namespace Coverlet.Core.Instrumentation.Tests
{ {
public class InstrumenterTests : IDisposable public class InstrumenterTests : IDisposable
{ {
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>(); private readonly Mock<ILogger> _mockLogger = new();
private Action disposeAction; private Action _disposeAction;
public void Dispose() public void Dispose()
{ {
disposeAction?.Invoke(); _disposeAction?.Invoke();
} }
[ConditionalFact] [ConditionalFact]
@@ -192,9 +192,7 @@ namespace Coverlet.Core.Instrumentation.Tests
Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc); Assert.NotNull(doc);
#pragma warning disable CS0612 // Type or member is obsolete
bool found = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::Method(System.String)")); bool found = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::Method(System.String)"));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(found, "Method decorated with with exclude attribute should be excluded"); Assert.False(found, "Method decorated with with exclude attribute should be excluded");
instrumenterTest.Directory.Delete(true); instrumenterTest.Directory.Delete(true);
@@ -211,14 +209,10 @@ namespace Coverlet.Core.Instrumentation.Tests
Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs"); Document doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Samples.cs");
Assert.NotNull(doc); Assert.NotNull(doc);
#pragma warning disable CS0612 // Type or member is obsolete
bool getFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::get_Property()")); bool getFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::get_Property()"));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(getFound, "Property getter decorated with with exclude attribute should be excluded"); Assert.False(getFound, "Property getter decorated with with exclude attribute should be excluded");
#pragma warning disable CS0612 // Type or member is obsolete
bool setFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::set_Property()")); bool setFound = doc.Lines.Values.Any(l => l.Method.Equals($"System.String Coverlet.Core.Samples.Tests.{testClassName}::set_Property()"));
#pragma warning restore CS0612 // Type or member is obsolete
Assert.False(setFound, "Property setter decorated with with exclude attribute should be excluded"); Assert.False(setFound, "Property setter decorated with with exclude attribute should be excluded");
instrumenterTest.Directory.Delete(true); instrumenterTest.Directory.Delete(true);
@@ -587,7 +581,7 @@ public class SampleClass
string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); string tempDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
Directory.CreateDirectory(tempDirectory); Directory.CreateDirectory(tempDirectory);
disposeAction = () => Directory.Delete(tempDirectory, true); _disposeAction = () => Directory.Delete(tempDirectory, true);
var partialMockFileSystem = new Mock<FileSystem>(); var partialMockFileSystem = new Mock<FileSystem>();
partialMockFileSystem.CallBase = true; partialMockFileSystem.CallBase = true;
@@ -30,7 +30,7 @@ namespace Coverlet.Core.Tests.Instrumentation
public class ModuleTrackerTemplateTests : ExternalProcessExecutionTest public class ModuleTrackerTemplateTests : ExternalProcessExecutionTest
{ {
private static readonly Task<int> _success = Task.FromResult(0); private static readonly Task<int> s_success = Task.FromResult(0);
[Fact] [Fact]
public void HitsFileCorrectlyWritten() public void HitsFileCorrectlyWritten()
@@ -44,7 +44,7 @@ namespace Coverlet.Core.Tests.Instrumentation
int[] expectedHitsArray = new[] { 1, 2, 0, 3 }; int[] expectedHitsArray = new[] { 1, 2, 0, 3 };
Assert.Equal(expectedHitsArray, ReadHitsFile()); Assert.Equal(expectedHitsArray, ReadHitsFile());
return _success; return s_success;
}); });
} }
@@ -57,7 +57,7 @@ namespace Coverlet.Core.Tests.Instrumentation
WriteHitsFile(new[] { 1, 2, 3 }); WriteHitsFile(new[] { 1, 2, 3 });
ModuleTrackerTemplate.HitsArray = new[] { 1 }; ModuleTrackerTemplate.HitsArray = new[] { 1 };
Assert.Throws<InvalidOperationException>(() => ModuleTrackerTemplate.UnloadModule(null, null)); Assert.Throws<InvalidOperationException>(() => ModuleTrackerTemplate.UnloadModule(null, null));
return _success; return s_success;
}); });
} }
@@ -94,7 +94,7 @@ namespace Coverlet.Core.Tests.Instrumentation
} }
} }
return _success; return s_success;
}); });
} }
@@ -113,7 +113,7 @@ namespace Coverlet.Core.Tests.Instrumentation
int[] expectedHitsArray = new[] { 0, 4, 4, 4 }; int[] expectedHitsArray = new[] { 0, 4, 4, 4 };
Assert.Equal(expectedHitsArray, ReadHitsFile()); Assert.Equal(expectedHitsArray, ReadHitsFile());
return _success; return s_success;
}); });
} }
@@ -149,32 +149,28 @@ namespace Coverlet.Core.Tests.Instrumentation
} }
private void WriteHitsFile(int[] hitsArray) private static void WriteHitsFile(int[] hitsArray)
{ {
using (var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Create)) using var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Create);
using (var bw = new BinaryWriter(fs)) using var bw = new BinaryWriter(fs);
bw.Write(hitsArray.Length);
foreach (int hitCount in hitsArray)
{ {
bw.Write(hitsArray.Length); bw.Write(hitCount);
foreach (int hitCount in hitsArray)
{
bw.Write(hitCount);
}
} }
} }
private int[] ReadHitsFile() private static int[] ReadHitsFile()
{ {
using (var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Open)) using var fs = new FileStream(ModuleTrackerTemplate.HitsFilePath, FileMode.Open);
using (var br = new BinaryReader(fs)) using var br = new BinaryReader(fs);
int[] hitsArray = new int[br.ReadInt32()];
for (int i = 0; i < hitsArray.Length; ++i)
{ {
int[] hitsArray = new int[br.ReadInt32()]; hitsArray[i] = br.ReadInt32();
for (int i = 0; i < hitsArray.Length; ++i)
{
hitsArray[i] = br.ReadInt32();
}
return hitsArray;
} }
return hitsArray;
} }
} }
} }
@@ -41,7 +41,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(2, points.Count()); Assert.Equal(2, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(0, points[0].Path); Assert.Equal(0, points[0].Path);
Assert.Equal(1, points[1].Path); Assert.Equal(1, points[1].Path);
@@ -61,7 +61,7 @@ namespace Coverlet.Core.Symbols.Tests
// act // act
System.Collections.Generic.IReadOnlyList<BranchPoint> points = _cecilSymbolHelper.GetBranchPoints(method); System.Collections.Generic.IReadOnlyList<BranchPoint> points = _cecilSymbolHelper.GetBranchPoints(method);
Assert.Equal(2, points.Count()); Assert.Equal(2, points.Count);
} }
[Fact] [Fact]
@@ -89,7 +89,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(4, points.Count()); Assert.Equal(4, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[2].Offset, points[3].Offset); Assert.Equal(points[2].Offset, points[3].Offset);
Assert.Equal(28, points[0].StartLine); Assert.Equal(28, points[0].StartLine);
@@ -108,7 +108,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(2, points.Count()); Assert.Equal(2, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(35, points[0].StartLine); Assert.Equal(35, points[0].StartLine);
Assert.Equal(35, points[1].StartLine); Assert.Equal(35, points[1].StartLine);
@@ -127,7 +127,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(4, points.Count()); Assert.Equal(4, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(3, points[3].Path); Assert.Equal(3, points[3].Path);
@@ -150,7 +150,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(4, points.Count()); Assert.Equal(4, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(3, points[3].Path); Assert.Equal(3, points[3].Path);
@@ -173,7 +173,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(4, points.Count()); Assert.Equal(4, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(3, points[3].Path); Assert.Equal(3, points[3].Path);
@@ -196,7 +196,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(4, points.Count()); Assert.Equal(4, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[0].Offset, points[2].Offset); Assert.Equal(points[0].Offset, points[2].Offset);
Assert.Equal(points[0].Offset, points[3].Offset); Assert.Equal(points[0].Offset, points[3].Offset);
@@ -351,7 +351,7 @@ namespace Coverlet.Core.Symbols.Tests
// We do expect there to be a two-way branch (stay in the loop or not?) on // We do expect there to be a two-way branch (stay in the loop or not?) on
// the line containing "await foreach". // the line containing "await foreach".
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(2, points.Count()); Assert.Equal(2, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(204, points[0].StartLine); Assert.Equal(204, points[0].StartLine);
Assert.Equal(204, points[1].StartLine); Assert.Equal(204, points[1].StartLine);
@@ -375,7 +375,7 @@ namespace Coverlet.Core.Symbols.Tests
// containing "await foreach" and the other being the "if" statement inside // containing "await foreach" and the other being the "if" statement inside
// the loop. // the loop.
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(4, points.Count()); Assert.Equal(4, points.Count);
Assert.Equal(points[0].Offset, points[1].Offset); Assert.Equal(points[0].Offset, points[1].Offset);
Assert.Equal(points[2].Offset, points[3].Offset); Assert.Equal(points[2].Offset, points[3].Offset);
Assert.Equal(219, points[0].StartLine); Assert.Equal(219, points[0].StartLine);
@@ -399,7 +399,7 @@ namespace Coverlet.Core.Symbols.Tests
// assert // assert
// We do expect the "for" loop to be a branch with two branch points, but that's it. // We do expect the "for" loop to be a branch with two branch points, but that's it.
Assert.NotNull(points); Assert.NotNull(points);
Assert.Equal(2, points.Count()); Assert.Equal(2, points.Count);
Assert.Equal(237, points[0].StartLine); Assert.Equal(237, points[0].StartLine);
Assert.Equal(237, points[1].StartLine); Assert.Equal(237, points[1].StartLine);
} }
+2 -2
View File
@@ -24,7 +24,7 @@ namespace Coverlet.Integration.Tests
public abstract class BaseTest public abstract class BaseTest
{ {
private static int _folderSuffix = 0; private static int s_folderSuffix;
protected BuildConfiguration GetAssemblyBuildConfiguration() protected BuildConfiguration GetAssemblyBuildConfiguration()
{ {
@@ -67,7 +67,7 @@ namespace Coverlet.Integration.Tests
private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispose = true, string testSDKVersion = "16.5.0") private protected ClonedTemplateProject CloneTemplateProject(bool cleanupOnDispose = true, string testSDKVersion = "16.5.0")
{ {
DirectoryInfo finalRoot = Directory.CreateDirectory($"{Guid.NewGuid().ToString("N").Substring(0, 6)}{Interlocked.Increment(ref _folderSuffix)}"); DirectoryInfo finalRoot = Directory.CreateDirectory($"{Guid.NewGuid().ToString("N")[..6]}{Interlocked.Increment(ref s_folderSuffix)}");
foreach (string file in (Directory.GetFiles($"../../../../coverlet.integration.template", "*.cs") foreach (string file in (Directory.GetFiles($"../../../../coverlet.integration.template", "*.cs")
.Union(Directory.GetFiles($"../../../../coverlet.integration.template", "*.csproj") .Union(Directory.GetFiles($"../../../../coverlet.integration.template", "*.csproj")
.Union(Directory.GetFiles($"../../../../coverlet.integration.template", "nuget.config"))))) .Union(Directory.GetFiles($"../../../../coverlet.integration.template", "nuget.config")))))