Check nested types for exclude filter (#694)
Check nested types for exclude filter
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
<!-- Do not upgrade this version or we won't support old SDK -->
|
||||
<PackageReference Update="Newtonsoft.Json" Version="9.0.1" />
|
||||
<PackageReference Update="NuGet.Packaging" Version="5.4.0" />
|
||||
<PackageReference Update="ReportGenerator.Core" Version="4.2.15" />
|
||||
<PackageReference Update="ReportGenerator.Core" Version="4.4.4" />
|
||||
<!--
|
||||
Do not change System.Reflection.Metadata version since we need to support VSTest DataCollectors. Goto https://www.nuget.org/packages/System.Reflection.Metadata to check versions.
|
||||
We need to load assembly version 1.4.2.0 to properly work
|
||||
|
||||
@@ -141,6 +141,28 @@ namespace Coverlet.Core.Instrumentation
|
||||
return _result;
|
||||
}
|
||||
|
||||
// If current type or one of his parent is excluded we'll exclude it
|
||||
// If I'm out every my children and every children of my children will be out
|
||||
private bool IsTypeExcluded(TypeDefinition type)
|
||||
{
|
||||
for (TypeDefinition current = type; current != null; current = current.DeclaringType)
|
||||
{
|
||||
// Check exclude attribute and filters
|
||||
if (current.CustomAttributes.Any(IsExcludeAttribute) || _instrumentationHelper.IsTypeExcluded(_module, current.FullName, _excludeFilters))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Instrumenting Interlocked which is used for recording hits would cause an infinite loop.
|
||||
private bool Is_System_Threading_Interlocked_CoreLib_Type(TypeDefinition type)
|
||||
{
|
||||
return _isCoreLibrary && type.FullName == "System.Threading.Interlocked";
|
||||
}
|
||||
|
||||
private void InstrumentModule()
|
||||
{
|
||||
using (var stream = _fileSystem.NewFileStream(_module, FileMode.Open, FileAccess.ReadWrite))
|
||||
@@ -177,18 +199,15 @@ namespace Coverlet.Core.Instrumentation
|
||||
|
||||
foreach (TypeDefinition type in types)
|
||||
{
|
||||
var actualType = type.DeclaringType ?? type;
|
||||
if (!actualType.CustomAttributes.Any(IsExcludeAttribute)
|
||||
// Instrumenting Interlocked which is used for recording hits would cause an infinite loop.
|
||||
&& (!_isCoreLibrary || actualType.FullName != "System.Threading.Interlocked")
|
||||
&& !_instrumentationHelper.IsTypeExcluded(_module, actualType.FullName, _excludeFilters)
|
||||
&& _instrumentationHelper.IsTypeIncluded(_module, actualType.FullName, _includeFilters)
|
||||
if (
|
||||
!Is_System_Threading_Interlocked_CoreLib_Type(type) &&
|
||||
!IsTypeExcluded(type) &&
|
||||
_instrumentationHelper.IsTypeIncluded(_module, type.FullName, _includeFilters)
|
||||
)
|
||||
{
|
||||
if (IsSynthesizedMemberToBeExcluded(type))
|
||||
{
|
||||
_excludedCompilerGeneratedTypes ??= new List<string>();
|
||||
_excludedCompilerGeneratedTypes.Add(type.FullName);
|
||||
(_excludedCompilerGeneratedTypes ??= new List<string>()).Add(type.FullName);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -378,8 +397,7 @@ namespace Coverlet.Core.Instrumentation
|
||||
}
|
||||
else
|
||||
{
|
||||
_excludedMethods ??= new List<(MethodDefinition, int)>();
|
||||
_excludedMethods.Add((method, ordinal));
|
||||
(_excludedMethods ??= new List<(MethodDefinition, int)>()).Add((method, ordinal));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -78,7 +78,7 @@ namespace Coverlet.Core.Instrumentation
|
||||
{
|
||||
WriteLog($"Unload called for '{Assembly.GetExecutingAssembly().Location}'");
|
||||
// Claim the current hits array and reset it to prevent double-counting scenarios.
|
||||
var hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]);
|
||||
int[] hitsArray = Interlocked.Exchange(ref HitsArray, new int[HitsArray.Length]);
|
||||
|
||||
// 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.
|
||||
@@ -101,8 +101,9 @@ namespace Coverlet.Core.Instrumentation
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
WriteLog($"Failed to create new hits file '{HitsFilePath}'\n{ex}");
|
||||
failedToCreateNewHitsFile = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Coverlet.Core.Abstracts;
|
||||
@@ -99,7 +100,7 @@ namespace Coverlet.Core.Tests
|
||||
|
||||
// For now we have only async Run helper
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
// we return 0 if we return something different assert fail
|
||||
return 0;
|
||||
@@ -138,7 +139,7 @@ namespace Coverlet.Core.Tests
|
||||
{
|
||||
instance.Switch(1);
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
@@ -176,7 +177,7 @@ namespace Coverlet.Core.Tests
|
||||
res = ((Task<int>)instance.ConfigureAwait()).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
@@ -227,7 +228,7 @@ namespace Coverlet.Core.Tests
|
||||
instance.InvokeAnonymous_Test();
|
||||
((Task<bool>)instance.InvokeAnonymousAsync_Test()).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
@@ -273,7 +274,7 @@ namespace Coverlet.Core.Tests
|
||||
{
|
||||
((Task<int>)instance.Test("test")).ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -314,7 +315,7 @@ namespace Coverlet.Core.Tests
|
||||
{
|
||||
instance.Test();
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -344,7 +345,7 @@ namespace Coverlet.Core.Tests
|
||||
{
|
||||
instance.Test("test");
|
||||
return Task.CompletedTask;
|
||||
}, pathSerialize);
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
|
||||
@@ -361,5 +362,150 @@ namespace Coverlet.Core.Tests
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFromCodeCoverageNextedTypes()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
RemoteExecutor.Invoke(async pathSerialize =>
|
||||
{
|
||||
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ExcludeFromCoverageAttrFilterClass1>(instance =>
|
||||
{
|
||||
Assert.Equal(42, instance.Run());
|
||||
return Task.CompletedTask;
|
||||
}, persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
TestInstrumentationHelper.GetCoverageResult(path)
|
||||
.Document("Instrumentation.ExcludeFromCoverage.cs")
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (143, 1))
|
||||
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 146, 160);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFilteredNestedAutogeneratedTypes()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
RemoteExecutor.Invoke(async pathSerialize =>
|
||||
{
|
||||
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ExcludeFilterNestedAutogeneratedTypes>(instance =>
|
||||
{
|
||||
instance.Run();
|
||||
|
||||
PropertyInfo stateProp = null;
|
||||
foreach (Type type in ((Type)instance.GetType()).Assembly.GetTypes())
|
||||
{
|
||||
if (typeof(Issue_689).FullName == type.FullName)
|
||||
{
|
||||
Assert.Equal(0, (stateProp = type.GetProperty("State")).GetValue(null));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Type type in ((Type)instance.GetType()).Assembly.GetTypes())
|
||||
{
|
||||
if (typeof(EventSource_Issue_689).FullName == type.FullName)
|
||||
{
|
||||
type.GetMethod("RaiseEvent").Invoke(null, null);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.Equal(2, stateProp.GetValue(null));
|
||||
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
includeFilter: moduleFileName => new string[] { $"[{Path.GetFileNameWithoutExtension(moduleFileName)}*]*ExcludeFilterNestedAutogeneratedTypes", $"[{Path.GetFileNameWithoutExtension(moduleFileName)}*]*Issue_689" },
|
||||
excludeFilter: moduleFileName => new string[] { $"[{Path.GetFileNameWithoutExtension(moduleFileName)}*]*NestedToFilterOut", $"[{Path.GetFileNameWithoutExtension(moduleFileName)}*]*Uncoverlet" },
|
||||
persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
TestInstrumentationHelper.GetCoverageResult(path)
|
||||
.Document("Instrumentation.ExcludeFilter.cs")
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (12, 1), (13, 1), (14, 1))
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (27, 1), (28, 1), (29, 1), (30, 1), (31, 1))
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (39, 2), (40, 2), (41, 2), (43, 5))
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (50, 1), (51, 1), (52, 1))
|
||||
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 17, 21)
|
||||
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 33, 36);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFilteredTypes()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
RemoteExecutor.Invoke(async pathSerialize =>
|
||||
{
|
||||
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ExcludeFilterOuterTypes>(instance =>
|
||||
{
|
||||
Assert.Equal(42, instance.Run());
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
excludeFilter: moduleFileName => new string[] { $"[{Path.GetFileNameWithoutExtension(moduleFileName)}*]*ExcludeFilterOuterTypes" },
|
||||
persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
TestInstrumentationHelper.GetCoverageResult(path)
|
||||
.Document("Instrumentation.ExcludeFilter.cs")
|
||||
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 1, 62)
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (66, 1), (68, 1));
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExcludeFilteredNestedTypes()
|
||||
{
|
||||
string path = Path.GetTempFileName();
|
||||
try
|
||||
{
|
||||
RemoteExecutor.Invoke(async pathSerialize =>
|
||||
{
|
||||
CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run<ExcludeFilterClass1>(instance =>
|
||||
{
|
||||
Assert.Equal(42, instance.Run());
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
excludeFilter: moduleFileName => new string[] { $"[{Path.GetFileNameWithoutExtension(moduleFileName)}*]*ExcludeFilterClass2" },
|
||||
persistPrepareResultToFile: pathSerialize);
|
||||
|
||||
return 0;
|
||||
}, path).Dispose();
|
||||
|
||||
TestInstrumentationHelper.GetCoverageResult(path)
|
||||
.Document("Instrumentation.ExcludeFilter.cs")
|
||||
.AssertLinesCovered(BuildConfiguration.Debug, (73, 1))
|
||||
.AssertNonInstrumentedLines(BuildConfiguration.Debug, 75, 93);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ using Coverlet.Core.Reporters;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Moq;
|
||||
using Palmmedia.ReportGenerator.Core;
|
||||
using Xunit;
|
||||
using Xunit.Sdk;
|
||||
|
||||
namespace Coverlet.Core.Tests
|
||||
@@ -28,6 +29,39 @@ namespace Coverlet.Core.Tests
|
||||
|
||||
static class TestInstrumentationAssert
|
||||
{
|
||||
public static CoverageResult GenerateReport(this CoverageResult coverageResult, [CallerMemberName]string directory = "")
|
||||
{
|
||||
if (coverageResult is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(coverageResult));
|
||||
}
|
||||
|
||||
TestInstrumentationHelper.GenerateHtmlReport(coverageResult, directory: directory);
|
||||
|
||||
return coverageResult;
|
||||
}
|
||||
|
||||
public static bool IsPresent(this CoverageResult coverageResult, string docName)
|
||||
{
|
||||
if (docName is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(docName));
|
||||
}
|
||||
|
||||
foreach (InstrumenterResult instrumenterResult in coverageResult.InstrumentedResults)
|
||||
{
|
||||
foreach (KeyValuePair<string, Document> document in instrumenterResult.Documents)
|
||||
{
|
||||
if (Path.GetFileName(document.Key) == docName)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public static Document Document(this CoverageResult coverageResult, string docName)
|
||||
{
|
||||
if (docName is null)
|
||||
@@ -279,7 +313,7 @@ namespace Coverlet.Core.Tests
|
||||
reportFile = Path.Combine(dir.FullName, Path.ChangeExtension("report", reporter.Extension));
|
||||
File.WriteAllText(reportFile, reporter.Report(coverageResult));
|
||||
// i.e. reportgenerator -reports:"C:\git\coverlet\test\coverlet.core.tests\bin\Debug\netcoreapp2.0\Condition_If\report.cobertura.xml" -targetdir:"C:\git\coverlet\test\coverlet.core.tests\bin\Debug\netcoreapp2.0\Condition_If" -filefilters:+**\Samples\Instrumentation.cs
|
||||
new Generator().GenerateReport(new ReportConfiguration(
|
||||
Assert.True(new Generator().GenerateReport(new ReportConfiguration(
|
||||
new[] { reportFile },
|
||||
dir.FullName,
|
||||
new string[0],
|
||||
@@ -290,21 +324,29 @@ namespace Coverlet.Core.Tests
|
||||
new string[0],
|
||||
string.IsNullOrEmpty(sourceFileFilter) ? new string[0] : new[] { sourceFileFilter },
|
||||
null,
|
||||
null));
|
||||
null)));
|
||||
}
|
||||
|
||||
public static CoverageResult GetCoverageResult(string filePath)
|
||||
{
|
||||
using (var result = new FileStream(filePath, FileMode.Open))
|
||||
using var result = new FileStream(filePath, FileMode.Open);
|
||||
var logger = new Mock<ILogger>();
|
||||
logger.Setup(l => l.LogVerbose(It.IsAny<string>())).Callback((string message) =>
|
||||
{
|
||||
CoveragePrepareResult coveragePrepareResultLoaded = CoveragePrepareResult.Deserialize(result);
|
||||
Coverage coverage = new Coverage(coveragePrepareResultLoaded, new Mock<ILogger>().Object, DependencyInjection.Current.GetService<IInstrumentationHelper>(), new FileSystem());
|
||||
return coverage.GetCoverageResult();
|
||||
}
|
||||
Assert.DoesNotContain("not found for module: ", message);
|
||||
});
|
||||
CoveragePrepareResult coveragePrepareResultLoaded = CoveragePrepareResult.Deserialize(result);
|
||||
Coverage coverage = new Coverage(coveragePrepareResultLoaded, logger.Object, DependencyInjection.Current.GetService<IInstrumentationHelper>(), new FileSystem());
|
||||
return coverage.GetCoverageResult();
|
||||
}
|
||||
|
||||
async public static Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callMethod, string persistPrepareResultToFile, bool disableRestoreModules = false)
|
||||
async public static Task<CoveragePrepareResult> Run<T>(Func<dynamic, Task> callMethod, Func<string, string[]> includeFilter = null, Func<string, string[]> excludeFilter = null, string persistPrepareResultToFile = null, bool disableRestoreModules = false)
|
||||
{
|
||||
if (persistPrepareResultToFile is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(persistPrepareResultToFile));
|
||||
}
|
||||
|
||||
var serviceCollection = new ServiceCollection();
|
||||
serviceCollection.AddTransient<IRetryHelper, CustomRetryHelper>();
|
||||
serviceCollection.AddTransient<IProcessExitHandler, CustomProcessExitHandler>();
|
||||
@@ -331,20 +373,24 @@ namespace Coverlet.Core.Tests
|
||||
File.Copy(location, newPath);
|
||||
File.Copy(Path.ChangeExtension(location, ".pdb"), Path.ChangeExtension(newPath, ".pdb"));
|
||||
|
||||
static string[] defaultFilters(string _) => Array.Empty<string>();
|
||||
// Instrument module
|
||||
Coverage coverage = new Coverage(newPath,
|
||||
includeFilters: (includeFilter is null ? defaultFilters(fileName) : includeFilter(fileName)).Concat(
|
||||
new string[]
|
||||
{
|
||||
$"[{Path.GetFileNameWithoutExtension(fileName)}*]{typeof(T).FullName}*"
|
||||
},
|
||||
}).ToArray(),
|
||||
Array.Empty<string>(),
|
||||
new string[]
|
||||
excludeFilters: (excludeFilter is null ? defaultFilters(fileName) : excludeFilter(fileName)).Concat(new string[]
|
||||
{
|
||||
"[xunit.*]*",
|
||||
"[coverlet.*]*"
|
||||
}, Array.Empty<string>(), Array.Empty<string>(), true, false, "", false, new Logger(logFile), DependencyInjection.Current.GetService<IInstrumentationHelper>(), DependencyInjection.Current.GetService<IFileSystem>());
|
||||
}).ToArray(), Array.Empty<string>(), Array.Empty<string>(), true, false, "", false, new Logger(logFile), DependencyInjection.Current.GetService<IInstrumentationHelper>(), DependencyInjection.Current.GetService<IFileSystem>());
|
||||
CoveragePrepareResult prepareResult = coverage.PrepareModules();
|
||||
|
||||
Assert.Single(prepareResult.Results);
|
||||
|
||||
// Load new assembly
|
||||
Assembly asm = Assembly.LoadFile(newPath);
|
||||
|
||||
@@ -354,13 +400,17 @@ namespace Coverlet.Core.Tests
|
||||
// Flush tracker
|
||||
Type tracker = asm.GetTypes().Single(n => n.FullName.Contains("Coverlet.Core.Instrumentation.Tracker"));
|
||||
|
||||
// For debugging purpouse
|
||||
// int[] hitsArray = (int[])tracker.GetField("HitsArray").GetValue(null);
|
||||
// string hitsFilePath = (string)tracker.GetField("HitsFilePath").GetValue(null);
|
||||
|
||||
// Void UnloadModule(System.Object, System.EventArgs)
|
||||
tracker.GetTypeInfo().GetMethod("UnloadModule").Invoke(null, new object[2] { null, null });
|
||||
|
||||
// Persist CoveragePrepareResult
|
||||
using (FileStream fs = new FileStream(persistPrepareResultToFile, FileMode.Open))
|
||||
{
|
||||
CoveragePrepareResult.Serialize(prepareResult).CopyTo(fs);
|
||||
await CoveragePrepareResult.Serialize(prepareResult).CopyToAsync(fs);
|
||||
}
|
||||
|
||||
return prepareResult;
|
||||
|
||||
@@ -23,15 +23,16 @@ namespace Coverlet.Core.Instrumentation.Tests
|
||||
private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem());
|
||||
private readonly Mock<ILogger> _mockLogger = new Mock<ILogger>();
|
||||
|
||||
[Fact(Skip = "To be used only validating System.Private.CoreLib instrumentation")]
|
||||
[Fact]
|
||||
public void TestCoreLibInstrumentation()
|
||||
{
|
||||
// Attention: to run this test adjust the paths and copy the IL only version of corelib
|
||||
const string OriginalFilesDir = @"c:\s\tmp\Coverlet-CoreLib\Original\";
|
||||
const string TestFilesDir = @"c:\s\tmp\Coverlet-CoreLib\Test\";
|
||||
|
||||
Directory.CreateDirectory(TestFilesDir);
|
||||
// We test only on win because sample dll/pdb were build on it
|
||||
if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
DirectoryInfo directory = Directory.CreateDirectory(Path.Combine(Directory.GetCurrentDirectory(), nameof(TestCoreLibInstrumentation)));
|
||||
string[] files = new[]
|
||||
{
|
||||
"System.Private.CoreLib.dll",
|
||||
@@ -39,12 +40,54 @@ namespace Coverlet.Core.Instrumentation.Tests
|
||||
};
|
||||
|
||||
foreach (var file in files)
|
||||
File.Copy(Path.Combine(OriginalFilesDir, file), Path.Combine(TestFilesDir, file), overwrite: true);
|
||||
{
|
||||
File.Copy(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", file), Path.Combine(directory.FullName, file), overwrite: true);
|
||||
}
|
||||
|
||||
Mock<FileSystem> partialMockFileSystem = new Mock<FileSystem>();
|
||||
partialMockFileSystem.CallBase = true;
|
||||
partialMockFileSystem.Setup(fs => fs.OpenRead(It.IsAny<string>())).Returns((string path) =>
|
||||
{
|
||||
if (Path.GetFileName(path) == files[1])
|
||||
{
|
||||
return File.OpenRead(Path.Combine(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), files[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
return File.OpenRead(path);
|
||||
}
|
||||
});
|
||||
partialMockFileSystem.Setup(fs => fs.Exists(It.IsAny<string>())).Returns((string path) =>
|
||||
{
|
||||
if (Path.GetFileName(path) == files[1])
|
||||
{
|
||||
return File.Exists(Path.Combine(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), files[1]));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (path.Contains(@":\git\runtime"))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return File.Exists(path);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), partialMockFileSystem.Object);
|
||||
Instrumenter instrumenter = new Instrumenter(Path.Combine(directory.FullName, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false, _mockLogger.Object, instrumentationHelper, partialMockFileSystem.Object);
|
||||
|
||||
Instrumenter instrumenter = new Instrumenter(Path.Combine(TestFilesDir, files[0]), "_coverlet_instrumented", Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), Array.Empty<string>(), false, _mockLogger.Object, _instrumentationHelper, new FileSystem());
|
||||
Assert.True(instrumenter.CanInstrument());
|
||||
var result = instrumenter.Instrument();
|
||||
InstrumenterResult result = instrumenter.Instrument();
|
||||
Assert.NotNull(result);
|
||||
Assert.Equal(1052, result.Documents.Count);
|
||||
foreach ((string docName, Document _) in result.Documents)
|
||||
{
|
||||
Assert.False(docName.EndsWith(@"System.Private.CoreLib\src\System\Threading\Interlocked.cs"));
|
||||
}
|
||||
directory.Delete(true);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -472,7 +515,7 @@ namespace Coverlet.Core.Instrumentation.Tests
|
||||
|
||||
Assert.Contains(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLambda(System.String,System.Int32)");
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr::TestLambda(System.String)");
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") &&
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") &&
|
||||
instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestLambda", 0));
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Method == "System.Int32 Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2::TestLambda(System.String,System.Int32)");
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") &&
|
||||
@@ -516,9 +559,9 @@ namespace Coverlet.Core.Instrumentation.Tests
|
||||
var doc = result.Documents.Values.FirstOrDefault(d => Path.GetFileName(d.Path) == "Instrumentation.ExcludeFromCoverage.cs");
|
||||
Assert.NotNull(doc);
|
||||
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") &&
|
||||
Assert.DoesNotContain(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") &&
|
||||
instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 2));
|
||||
Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") &&
|
||||
Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr/") &&
|
||||
instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 3));
|
||||
Assert.Contains(doc.Lines.Values, l => l.Class.StartsWith("Coverlet.Core.Samples.Tests.MethodsWithExcludeFromCodeCoverageAttr2/") &&
|
||||
instrumenterTest.Instrumenter.IsSynthesizedNameOf(l.Method, "TestYield", 2));
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Coverlet.Core.Instrumentation;
|
||||
using Coverlet.Tests.RemoteExecutor;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
@@ -7,111 +8,133 @@ using Xunit;
|
||||
|
||||
namespace Coverlet.Core.Tests.Instrumentation
|
||||
{
|
||||
public class ModuleTrackerTemplateTestsFixture : IDisposable
|
||||
class TrackerContext : IDisposable
|
||||
{
|
||||
public ModuleTrackerTemplateTestsFixture()
|
||||
public TrackerContext()
|
||||
{
|
||||
ModuleTrackerTemplate.HitsFilePath = Path.Combine(Path.GetTempPath(), nameof(ModuleTrackerTemplateTests));
|
||||
ModuleTrackerTemplate.HitsFilePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
File.Delete(ModuleTrackerTemplate.HitsFilePath);
|
||||
AppDomain.CurrentDomain.ProcessExit -= ModuleTrackerTemplate.UnloadModule;
|
||||
AppDomain.CurrentDomain.DomainUnload -= ModuleTrackerTemplate.UnloadModule;
|
||||
}
|
||||
}
|
||||
|
||||
public class ModuleTrackerTemplateTests : IClassFixture<ModuleTrackerTemplateTestsFixture>, IDisposable
|
||||
public class ModuleTrackerTemplateTests
|
||||
{
|
||||
|
||||
public ModuleTrackerTemplateTests()
|
||||
{
|
||||
File.Delete(ModuleTrackerTemplate.HitsFilePath);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
File.Delete(ModuleTrackerTemplate.HitsFilePath);
|
||||
}
|
||||
private static readonly Task<int> _success = Task.FromResult(0);
|
||||
|
||||
[Fact]
|
||||
public void HitsFileCorrectlyWritten()
|
||||
{
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 1, 2, 0, 3 };
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
using var invoker = RemoteExecutor.Invoke(_ =>
|
||||
{
|
||||
using var ctx = new TrackerContext();
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 1, 2, 0, 3 };
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
|
||||
var expectedHitsArray = new[] { 1, 2, 0, 3 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
var expectedHitsArray = new[] { 1, 2, 0, 3 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
|
||||
return _success;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HitsFileWithDifferentNumberOfEntriesCausesExceptionOnUnload()
|
||||
{
|
||||
WriteHitsFile(new[] { 1, 2, 3 });
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 1 };
|
||||
Assert.Throws<InvalidOperationException>(() => ModuleTrackerTemplate.UnloadModule(null, null));
|
||||
using var invoker = RemoteExecutor.Invoke(_ =>
|
||||
{
|
||||
using var ctx = new TrackerContext();
|
||||
WriteHitsFile(new[] { 1, 2, 3 });
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 1 };
|
||||
Assert.Throws<InvalidOperationException>(() => ModuleTrackerTemplate.UnloadModule(null, null));
|
||||
return _success;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact(Skip = "Failed CI Job: https://ci.appveyor.com/project/tonerdo/coverlet/builds/21145989/job/9gx5jnjs502vy1fv")]
|
||||
[Fact]
|
||||
public void HitsOnMultipleThreadsCorrectlyCounted()
|
||||
{
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 0, 0, 0 };
|
||||
for (int i = 0; i < ModuleTrackerTemplate.HitsArray.Length; ++i)
|
||||
using var invoker = RemoteExecutor.Invoke(_ =>
|
||||
{
|
||||
var t = new Thread(HitIndex);
|
||||
t.Start(i);
|
||||
}
|
||||
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
var expectedHitsArray = new[] { 4, 3, 2, 1 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
|
||||
void HitIndex(object index)
|
||||
{
|
||||
var hitIndex = (int)index;
|
||||
for (int i = 0; i <= hitIndex; ++i)
|
||||
using var ctx = new TrackerContext();
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 0, 0, 0 };
|
||||
for (int i = 0; i < ModuleTrackerTemplate.HitsArray.Length; ++i)
|
||||
{
|
||||
ModuleTrackerTemplate.RecordHit(i);
|
||||
var t = new Thread(HitIndex);
|
||||
t.Start(i);
|
||||
}
|
||||
}
|
||||
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
var expectedHitsArray = new[] { 4, 3, 2, 1 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
|
||||
static void HitIndex(object index)
|
||||
{
|
||||
var hitIndex = (int)index;
|
||||
for (int i = 0; i <= hitIndex; ++i)
|
||||
{
|
||||
ModuleTrackerTemplate.RecordHit(i);
|
||||
}
|
||||
}
|
||||
|
||||
return _success;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MultipleSequentialUnloadsHaveCorrectTotalData()
|
||||
{
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 3, 2, 1 };
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 };
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
|
||||
var expectedHitsArray = new[] { 0, 4, 4, 4 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async void MutexBlocksMultipleWriters()
|
||||
{
|
||||
using (var mutex = new Mutex(
|
||||
true, Path.GetFileNameWithoutExtension(ModuleTrackerTemplate.HitsFilePath) + "_Mutex", out bool createdNew))
|
||||
using var invoker = RemoteExecutor.Invoke(_ =>
|
||||
{
|
||||
Assert.True(createdNew);
|
||||
using var ctx = new TrackerContext();
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 3, 2, 1 };
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 };
|
||||
var unloadTask = Task.Run(() => ModuleTrackerTemplate.UnloadModule(null, null));
|
||||
|
||||
Assert.False(unloadTask.Wait(5));
|
||||
|
||||
WriteHitsFile(new[] { 0, 3, 2, 1 });
|
||||
|
||||
Assert.False(unloadTask.Wait(5));
|
||||
|
||||
mutex.ReleaseMutex();
|
||||
await unloadTask;
|
||||
ModuleTrackerTemplate.UnloadModule(null, null);
|
||||
|
||||
var expectedHitsArray = new[] { 0, 4, 4, 4 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
}
|
||||
|
||||
return _success;
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MutexBlocksMultipleWriters()
|
||||
{
|
||||
using var invoker = RemoteExecutor.Invoke(async _ =>
|
||||
{
|
||||
using var ctx = new TrackerContext();
|
||||
using (var mutex = new Mutex(
|
||||
true, Path.GetFileNameWithoutExtension(ModuleTrackerTemplate.HitsFilePath) + "_Mutex", out bool createdNew))
|
||||
{
|
||||
Assert.True(createdNew);
|
||||
|
||||
ModuleTrackerTemplate.HitsArray = new[] { 0, 1, 2, 3 };
|
||||
var unloadTask = Task.Run(() => ModuleTrackerTemplate.UnloadModule(null, null));
|
||||
|
||||
Assert.False(unloadTask.Wait(5));
|
||||
|
||||
WriteHitsFile(new[] { 0, 3, 2, 1 });
|
||||
|
||||
Assert.False(unloadTask.Wait(5));
|
||||
|
||||
mutex.ReleaseMutex();
|
||||
await unloadTask;
|
||||
|
||||
var expectedHitsArray = new[] { 0, 4, 4, 4 };
|
||||
Assert.Equal(expectedHitsArray, ReadHitsFile());
|
||||
}
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void WriteHitsFile(int[] hitsArray)
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
// Remember to use full name because adding new using directives change line numbers
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Coverlet.Core.Samples.Tests
|
||||
{
|
||||
public class ExcludeFilterNestedAutogeneratedTypes
|
||||
{
|
||||
public void Run()
|
||||
{
|
||||
NestedToFilterOut nested = new NestedToFilterOut();
|
||||
nested.SomeMethodLambda();
|
||||
nested.SomeMethodAsync().ConfigureAwait(false).GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
public class NestedToFilterOut
|
||||
{
|
||||
public void SomeMethodLambda() => AppDomain.CurrentDomain.ProcessExit += (s, e) => { };
|
||||
public Task<int> SomeMethodAsync() => Task.FromResult(new Random().Next());
|
||||
}
|
||||
}
|
||||
|
||||
public static class Issue_689
|
||||
{
|
||||
static Issue_689()
|
||||
{
|
||||
State = 0;
|
||||
EventSource_Issue_689.Handle += (s, e) => Handler(1);
|
||||
Uncoverlet.AddHandler();
|
||||
}
|
||||
|
||||
internal static class Uncoverlet
|
||||
{
|
||||
internal static void AddHandler() => EventSource_Issue_689.Handle += (s, e) => Handler(2);
|
||||
}
|
||||
|
||||
public static void Handler(int i)
|
||||
{
|
||||
State = i;
|
||||
}
|
||||
|
||||
public static int State { get; set; }
|
||||
}
|
||||
|
||||
public static class EventSource_Issue_689
|
||||
{
|
||||
public static event EventHandler<object> Handle;
|
||||
public static void RaiseEvent()
|
||||
{
|
||||
Handle?.Invoke(new object(), new object());
|
||||
}
|
||||
}
|
||||
|
||||
public class ExcludeFilterOuterTypes
|
||||
{
|
||||
public int Run()
|
||||
{
|
||||
return new ExcludeFilterOuterTypes2().Run();
|
||||
}
|
||||
}
|
||||
|
||||
public class ExcludeFilterOuterTypes2
|
||||
{
|
||||
public int Run()
|
||||
{
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
public class ExcludeFilterClass1
|
||||
{
|
||||
public int Run() => 10 + new ExcludeFilterClass2().Run();
|
||||
|
||||
public class ExcludeFilterClass2
|
||||
{
|
||||
public int Run() => 10 + new ExcludeFilterClass3().Run();
|
||||
|
||||
public class ExcludeFilterClass3
|
||||
{
|
||||
public int Run() => 10 + new ExcludeFilterClass4().Run();
|
||||
|
||||
public class ExcludeFilterClass4
|
||||
{
|
||||
public int Run() => 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,4 +137,25 @@ namespace Coverlet.Core.Samples.Tests
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class ExcludeFromCoverageAttrFilterClass1
|
||||
{
|
||||
public int Run() => 10 + new ExcludeFromCoverageAttrFilterClass2().Run();
|
||||
|
||||
[System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
|
||||
public class ExcludeFromCoverageAttrFilterClass2
|
||||
{
|
||||
public int Run() => 10 + new ExcludeFromCoverageAttrFilterClass3().Run();
|
||||
|
||||
public class ExcludeFromCoverageAttrFilterClass3
|
||||
{
|
||||
public int Run() => 10 + new ExcludeFromCoverageAttrFilterClass4().Run();
|
||||
|
||||
public class ExcludeFromCoverageAttrFilterClass4
|
||||
{
|
||||
public int Run() => 12;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
@@ -42,6 +42,12 @@
|
||||
<None Update="TestAssets\75d9f96508d74def860a568f426ea4a4.pdb">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestAssets\System.Private.CoreLib.dll">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="TestAssets\System.Private.CoreLib.pdb">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<Target Name="PostBuild" AfterTargets="PostBuildEvent">
|
||||
|
||||
@@ -335,7 +335,15 @@ namespace Coverlet.Integration.Tests
|
||||
{
|
||||
if (CleanupOnDispose)
|
||||
{
|
||||
Directory.Delete(ProjectRootPath, true);
|
||||
try
|
||||
{
|
||||
Directory.Delete(ProjectRootPath, true);
|
||||
}
|
||||
catch (UnauthorizedAccessException)
|
||||
{
|
||||
// Sometimes on CI AzDo we get Access Denied on delete
|
||||
// swallowed exception to not waste time
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,14 +20,9 @@ namespace Coverlet.Tests.RemoteExecutor
|
||||
// 1) Add a Debug.Launch() inside lambda and attach(slow)
|
||||
// 2) Temporary pass true to invoke local, it will throw because code try to replace locked files,
|
||||
// but if you temporary "comment" offensive code(RestoreOriginalModule/s) you can debug all procedure and it's very very very useful
|
||||
public static IRemoteInvokeHandle Invoke(Func<Task<int>> method, bool invokeLocal = false)
|
||||
public static IRemoteInvokeHandle Invoke(Func<string, Task<int>> method, string arg = "", bool invokeInProcess = false)
|
||||
{
|
||||
return Invoke(GetMethodInfo(method), Array.Empty<string>(), invokeLocal);
|
||||
}
|
||||
|
||||
public static IRemoteInvokeHandle Invoke(Func<string, Task<int>> method, string arg, bool invokeLocal = false)
|
||||
{
|
||||
if (invokeLocal)
|
||||
if (invokeInProcess)
|
||||
{
|
||||
return new LocalInvoker(method.Invoke(arg).GetAwaiter().GetResult());
|
||||
}
|
||||
@@ -83,19 +78,17 @@ namespace Coverlet.Tests.RemoteExecutor
|
||||
int ExitCode { get; }
|
||||
}
|
||||
|
||||
public class LocalInvoker : IRemoteInvokeHandle
|
||||
public struct LocalInvoker : IRemoteInvokeHandle
|
||||
{
|
||||
public int ExitCode => _result;
|
||||
public readonly int ExitCode { get; }
|
||||
|
||||
private int _result;
|
||||
|
||||
public LocalInvoker(int result) => _result = result;
|
||||
public LocalInvoker(int exitCode) => ExitCode = exitCode;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_result != 0)
|
||||
if (ExitCode != 0)
|
||||
{
|
||||
throw new RemoteExecutionException($"Result '{_result}'");
|
||||
throw new RemoteExecutionException($"Result '{ExitCode}'");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user