using System; using System.IO; using System.Threading.Tasks; using Coverlet.Core.Abstracts; using Coverlet.Core.Helpers; using Coverlet.Core.Logging; using Coverlet.Core.Samples.Tests; using Coverlet.Tests.RemoteExecutor; using Moq; using Xunit; namespace Coverlet.Core.Tests { public class CoverageTests { private readonly InstrumentationHelper _instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem()); private readonly Mock _mockLogger = new Mock(); [Fact] public void TestCoverage() { string module = GetType().Assembly.Location; string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); // TODO: Find a way to mimick hits var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), false, false, string.Empty, false, _mockLogger.Object, _instrumentationHelper, new FileSystem()); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.Empty(result.Modules); directory.Delete(true); } [Fact] public void TestCoverageWithTestAssembly() { string module = GetType().Assembly.Location; string pdb = Path.Combine(Path.GetDirectoryName(module), Path.GetFileNameWithoutExtension(module) + ".pdb"); var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString())); File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); File.Copy(pdb, Path.Combine(directory.FullName, Path.GetFileName(pdb)), true); // TODO: Find a way to mimick hits var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), Array.Empty(), true, false, string.Empty, false, _mockLogger.Object, _instrumentationHelper, new FileSystem()); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); } [Fact] public void SelectionStatements_If() { // We need to pass file name to remote process where it save instrumentation result // Similar to msbuild input/output string path = Path.GetTempFileName(); try { // Lambda will run in a custom process to avoid issue with statics and file locking RemoteExecutor.Invoke(async pathSerialize => { // Run load and call a delegate passing class as dynamic to simplify method call CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { // We call method to trigger coverage hits instance.If(true); // For now we have only async Run helper return Task.CompletedTask; }, pathSerialize); // we return 0 if we return something different assert fail return 0; }, path).Dispose(); // We retrive and load CoveragePrepareResult and run coverage calculation // Similar to msbuild coverage result task CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path); // Asserts on doc/lines/branches result.Document("Instrumentation.SelectionStatements.cs") // (line, hits) .AssertLinesCovered((11, 1), (15, 0)) // (line,ordinal,hits) .AssertBranchesCovered((9, 0, 1), (9, 1, 0)); // if need to generate html report for debugging purpose // TestInstrumentationHelper.GenerateHtmlReport(result); } finally { // Cleanup tmp file File.Delete(path); } } [Fact] public void SelectionStatements_Switch() { string path = Path.GetTempFileName(); try { RemoteExecutor.Invoke(async pathSerialize => { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { instance.Switch(1); return Task.CompletedTask; }, pathSerialize); return 0; }, path).Dispose(); CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path); result.Document("Instrumentation.SelectionStatements.cs") .AssertLinesCovered(BuildConfiguration.Release, (24, 1), (26, 0), (28, 0)) .AssertBranchesCovered(BuildConfiguration.Release, (24, 1, 1)) .AssertLinesCovered(BuildConfiguration.Debug, (20, 1), (21, 1), (24, 1), (30, 1)) .AssertBranchesCovered(BuildConfiguration.Debug, (21, 0, 0), (21, 1, 1), (21, 2, 0), (21, 3, 0)); } finally { File.Delete(path); } } [Fact] public void AsyncAwait() { string path = Path.GetTempFileName(); try { RemoteExecutor.Invoke(async pathSerialize => { CoveragePrepareResult coveragePrepareResult = await TestInstrumentationHelper.Run(instance => { instance.SyncExecution(); int res = ((Task)instance.AsyncExecution(true)).ConfigureAwait(false).GetAwaiter().GetResult(); res = ((Task)instance.AsyncExecution(1)).ConfigureAwait(false).GetAwaiter().GetResult(); res = ((Task)instance.AsyncExecution(2)).ConfigureAwait(false).GetAwaiter().GetResult(); res = ((Task)instance.AsyncExecution(3)).ConfigureAwait(false).GetAwaiter().GetResult(); res = ((Task)instance.ContinuationCalled()).ConfigureAwait(false).GetAwaiter().GetResult(); return Task.CompletedTask; }, pathSerialize); return 0; }, path).Dispose(); CoverageResult result = TestInstrumentationHelper.GetCoverageResult(path); result.Document("Instrumentation.AsyncAwait.cs") .AssertLinesCovered(BuildConfiguration.Debug, // AsyncExecution(bool) (10, 1), (11, 1), (12, 1), (14, 1), (16, 1), (17, 0), (18, 0), (19, 0), (21, 1), (22, 1), // Async (25, 9), (26, 9), (27, 9), (28, 9), // SyncExecution (31, 1), (32, 1), (33, 1), // Sync (36, 1), (37, 1), (38, 1), // AsyncExecution(int) (41, 3), (42, 3), (43, 3), (46, 1), (47, 1), (48, 1), (51, 1), (52, 1), (53, 1), (56, 1), (57, 1), (58, 1), (59, 1), (62, 0), (63, 0), (64, 0), (65, 0), (68, 0), (70, 3), (71, 3), // ContinuationNotCalled (74, 0), (75, 0), (76, 0), (77, 0), (78, 0), // ContinuationCalled -> line 83 should be 1 hit some issue with Continuation state machine (81, 1), (82, 1), (83, 2), (84, 1), (85, 1) ) .AssertBranchesCovered(BuildConfiguration.Debug, (16, 0, 0), (16, 1, 1), (43, 0, 3), (43, 1, 1), (43, 2, 1), (43, 3, 1), (43, 4, 0)) // Real branch should be 2, we should try to remove compiler generated branch in method ContinuationNotCalled/ContinuationCalled // for Continuation state machine .ExpectedTotalNumberOfBranches(BuildConfiguration.Debug, 4); } finally { File.Delete(path); } } } }