public override bool Execute() { WaitForDebuggerIfEnabled(); IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddTransient <IProcessExitHandler, ProcessExitHandler>(); serviceCollection.AddTransient <IFileSystem, FileSystem>(); serviceCollection.AddTransient <IConsole, SystemConsole>(); serviceCollection.AddTransient <ILogger, MSBuildLogger>(_ => _logger); serviceCollection.AddTransient <IRetryHelper, RetryHelper>(); // We cache resolutions serviceCollection.AddSingleton <ISourceRootTranslator, SourceRootTranslator>(serviceProvider => new SourceRootTranslator(_path, serviceProvider.GetRequiredService <ILogger>(), serviceProvider.GetRequiredService <IFileSystem>())); // We need to keep singleton/static semantics serviceCollection.AddSingleton <IInstrumentationHelper, InstrumentationHelper>(); ServiceProvider = serviceCollection.BuildServiceProvider(); try { var includeFilters = _include?.Split(','); var includeDirectories = _includeDirectory?.Split(','); var excludeFilters = _exclude?.Split(','); var excludedSourceFiles = _excludeByFile?.Split(','); var excludeAttributes = _excludeByAttribute?.Split(','); var fileSystem = ServiceProvider.GetService <IFileSystem>(); Coverage coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _includeTestAssembly, _singleHit, _mergeWith, _useSourceLink, _logger, ServiceProvider.GetService <IInstrumentationHelper>(), fileSystem, ServiceProvider.GetService <ISourceRootTranslator>()); CoveragePrepareResult prepareResult = coverage.PrepareModules(); InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); using (var instrumentedStateFile = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write)) { using (Stream serializedState = CoveragePrepareResult.Serialize(prepareResult)) { serializedState.CopyTo(instrumentedStateFile); } } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }
public void TestCoverageSkipModule__AssemblyMarkedAsExcludeFromCodeCoverage() { Mock <FileSystem> partialMockFileSystem = new Mock <FileSystem>(); partialMockFileSystem.CallBase = true; partialMockFileSystem.Setup(fs => fs.NewFileStream(It.IsAny <string>(), It.IsAny <FileMode>(), It.IsAny <FileAccess>())).Returns((string path, FileMode mode, FileAccess access) => { return(new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)); }); var loggerMock = new Mock <ILogger>(); string excludedbyattributeDll = Directory.GetFiles(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets"), "coverlet.tests.projectsample.excludedbyattribute.dll").First(); InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock <ILogger>().Object, new SourceRootTranslator(excludedbyattributeDll, new Mock <ILogger>().Object, new FileSystem())); // test skip module include test assembly feature var coverage = new Coverage(excludedbyattributeDll, new string[] { "[coverlet.tests.projectsample.excludedbyattribute*]*" }, Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), true, false, string.Empty, false, loggerMock.Object, instrumentationHelper, partialMockFileSystem.Object, new SourceRootTranslator(loggerMock.Object, new FileSystem())); CoveragePrepareResult result = coverage.PrepareModules(); Assert.Empty(result.Results); loggerMock.Verify(l => l.LogVerbose(It.IsAny <string>())); }
public override bool Execute() { try { var includeFilters = _include?.Split(','); var includeDirectories = _includeDirectory?.Split(','); var excludeFilters = _exclude?.Split(','); var excludedSourceFiles = _excludeByFile?.Split(','); var excludeAttributes = _excludeByAttribute?.Split(','); // We add default exclusion filter if no specified if (excludeFilters is null || excludeFilters.Length == 0) { excludeFilters = new string[] { "[xunit*]*" }; } Coverage coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _includeTestAssembly, _singleHit, _mergeWith, _useSourceLink, _logger); CoveragePrepareResult prepareResult = coverage.PrepareModules(); InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); using (var instrumentedStateFile = new FileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write)) { using (Stream serializedState = CoveragePrepareResult.Serialize(prepareResult)) { serializedState.CopyTo(instrumentedStateFile); } } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }
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: Mimic hits by calling ModuleTrackerTemplate.RecordHit before Unload // Since Coverage only instruments dependancies, we need a fake module here var testModule = Path.Combine(directory.FullName, "test.module.dll"); var coverage = new Coverage(testModule, Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), string.Empty, false); coverage.PrepareModules(); // The module hit tracker must signal to Coverage that it has done its job, so call it manually var instrumenterResult = coverage.Results.Single(); ModuleTrackerTemplate.HitsArray = new int[instrumenterResult.HitCandidates.Count + ModuleTrackerTemplate.HitsResultHeaderSize]; ModuleTrackerTemplate.HitsFilePath = instrumenterResult.HitsFilePath; ModuleTrackerTemplate.HitsMemoryMapName = instrumenterResult.HitsResultGuid; ModuleTrackerTemplate.UnloadModule(null, null); var result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); }
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); InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock <ILogger>().Object, new SourceRootTranslator(module, new Mock <ILogger>().Object, new FileSystem())); var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), true, false, string.Empty, false, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(module, _mockLogger.Object, new FileSystem())); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); }
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 // Since Coverage only instruments dependancies, we need a fake module here var testModule = Path.Combine(directory.FullName, "test.module.dll"); var coverage = new Coverage(testModule, Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), string.Empty); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); }
public override bool Execute() { try { var includeFilters = _include?.Split(','); var includeDirectories = _includeDirectory?.Split(','); var excludeFilters = _exclude?.Split(','); var excludedSourceFiles = _excludeByFile?.Split(','); var excludeAttributes = _excludeByAttribute?.Split(','); Coverage coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _includeTestAssembly, _singleHit, _mergeWith, _useSourceLink, _logger, (IInstrumentationHelper)DependencyInjection.Current.GetService(typeof(IInstrumentationHelper))); CoveragePrepareResult prepareResult = coverage.PrepareModules(); InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); using (var instrumentedStateFile = new FileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write)) { using (Stream serializedState = CoveragePrepareResult.Serialize(prepareResult)) { serializedState.CopyTo(instrumentedStateFile); } } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }
async public static Task <CoveragePrepareResult> Run <T>(Func <dynamic, Task> callMethod, string persistPrepareResultToFile) { // Setup correct retry helper to avoid exception in InstrumentationHelper.RestoreOriginalModules on remote process exit DependencyInjection.Set(new ServiceCollection() .AddTransient <IRetryHelper, CustomRetryHelper>() .AddTransient <IProcessExitHandler, CustomProcessExitHandler>() .BuildServiceProvider()); // Rename test file to avoid locks string location = typeof(T).Assembly.Location; string fileName = Path.ChangeExtension(Path.GetRandomFileName(), ".dll"); string logFile = Path.ChangeExtension(fileName, ".log"); string newPath = Path.Combine(Path.GetDirectoryName(location), fileName); File.Copy(location, newPath); File.Copy(Path.ChangeExtension(location, ".pdb"), Path.ChangeExtension(newPath, ".pdb")); // Instrument module Coverage coverage = new Coverage(newPath, new string[] { $"[{Path.GetFileNameWithoutExtension(fileName)}*]*" }, Array.Empty <string>(), new string[] { "[xunit.*]*", "[coverlet.*]*" }, Array.Empty <string>(), Array.Empty <string>(), true, false, "", false, new Logger(logFile)); CoveragePrepareResult prepareResult = coverage.PrepareModules(); // Load new assembly Assembly asm = Assembly.LoadFile(newPath); // Instance type and call method await callMethod(Activator.CreateInstance(asm.GetType(typeof(T).FullName))); // Flush tracker Type tracker = asm.GetTypes().Single(n => n.FullName.Contains("Coverlet.Core.Instrumentation.Tracker")); // 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); } return(prepareResult); }
public override bool Execute() { try { _coverage = new Coverage(_path, Guid.NewGuid().ToString()); _coverage.PrepareModules(); } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public override bool Execute() { try { var excludeRules = Exclude?.Split(','); Coverage = new Coverage(Path, Guid.NewGuid().ToString(), excludeRules); Coverage.PrepareModules(); } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public override bool Execute() { try { var rules = _excludeByFile?.Split(','); var filters = _exclude?.Split(','); _coverage = new Coverage(_path, Guid.NewGuid().ToString(), filters, rules); _coverage.PrepareModules(); } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public void TestCoverage() { string module = typeof(CoverageTests).Assembly.Location; string identifier = Guid.NewGuid().ToString(); var directory = Directory.CreateDirectory(Path.Combine(Path.GetTempPath(), identifier)); File.Copy(module, Path.Combine(directory.FullName, Path.GetFileName(module)), true); var coverage = new Coverage(module, identifier); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.Empty(result.Modules); directory.Delete(true); }
public override bool Execute() { try { var excludedSourceFiles = _excludeByFile?.Split(','); var excludeFilters = _exclude?.Split(','); var includeFilters = _include?.Split(','); _coverage = new Coverage(_path, excludeFilters, includeFilters, excludedSourceFiles, true); _coverage.PrepareModules(); } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public override bool Execute() { try { var includeFilters = _include?.Split(','); var includeDirectories = _includeDirectory?.Split(','); var excludeFilters = _exclude?.Split(','); var excludedSourceFiles = _excludeByFile?.Split(','); var excludeAttributes = _excludeByAttribute?.Split(','); _coverage = new Coverage(_path, includeFilters, includeDirectories, excludeFilters, excludedSourceFiles, excludeAttributes, _singleHit, _mergeWith, _useSourceLink); _coverage.PrepareModules(); } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
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 <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), Array.Empty <string>(), false, string.Empty, false, new Mock <ILogger>().Object); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); }
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); InstrumentationHelper instrumentationHelper = new InstrumentationHelper(new ProcessExitHandler(), new RetryHelper(), new FileSystem(), new Mock <ILogger>().Object, new SourceRootTranslator(module, new Mock <ILogger>().Object, new FileSystem())); CoverageParameters parameters = new CoverageParameters { IncludeFilters = Array.Empty <string>(), IncludeDirectories = Array.Empty <string>(), ExcludeFilters = Array.Empty <string>(), ExcludedSourceFiles = Array.Empty <string>(), ExcludeAttributes = Array.Empty <string>(), IncludeTestAssembly = true, SingleHit = false, MergeWith = string.Empty, UseSourceLink = false }; var coverage = new Coverage(Path.Combine(directory.FullName, Path.GetFileName(module)), parameters, _mockLogger.Object, instrumentationHelper, new FileSystem(), new SourceRootTranslator(module, _mockLogger.Object, new FileSystem()), new CecilSymbolHelper()); coverage.PrepareModules(); var result = coverage.GetCoverageResult(); Assert.NotEmpty(result.Modules); directory.Delete(true); }
static int Main(string[] args) { var logger = new ConsoleLogger(); var app = new CommandLineApplication(); app.Name = "coverlet"; app.FullName = "Cross platform .NET Core code coverage tool"; app.HelpOption("-h|--help"); app.VersionOption("-v|--version", GetAssemblyVersion()); CommandArgument module = app.Argument("<ASSEMBLY>", "Path to the test assembly."); CommandOption target = app.Option("-t|--target", "Path to the test runner application.", CommandOptionType.SingleValue); CommandOption targs = app.Option("-a|--targetargs", "Arguments to be passed to the test runner.", CommandOptionType.SingleValue); CommandOption output = app.Option("-o|--output", "Output of the generated coverage report", CommandOptionType.SingleValue); CommandOption formats = app.Option("-f|--format", "Format of the generated coverage report.", CommandOptionType.MultipleValue); CommandOption threshold = app.Option("--threshold", "Exits with error if the coverage % is below value.", CommandOptionType.SingleValue); CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", CommandOptionType.MultipleValue); CommandOption excludeFilters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); CommandOption includeFilters = app.Option("--include", "Filter expressions to include only specific modules and types.", CommandOptionType.MultipleValue); CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); app.OnExecute(() => { if (string.IsNullOrEmpty(module.Value) || string.IsNullOrWhiteSpace(module.Value)) { throw new CommandParsingException(app, "No test assembly specified."); } if (!target.HasValue()) { throw new CommandParsingException(app, "Target must be specified."); } Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), mergeWith.Value()); coverage.PrepareModules(); Process process = new Process(); process.StartInfo.FileName = target.Value(); process.StartInfo.Arguments = targs.HasValue() ? targs.Value() : string.Empty; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.Start(); logger.LogInformation(process.StandardOutput.ReadToEnd()); process.WaitForExit(); var dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); var dThreshold = threshold.HasValue() ? int.Parse(threshold.Value()) : 0; var dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List <string>(new string[] { "line", "branch", "method" }); logger.LogInformation("\nCalculating coverage result..."); var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(dOutput); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } foreach (var format in (formats.HasValue() ? formats.Values : new List <string>(new string[] { "json" }))) { var reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); } if (reporter.OutputType == ReporterOutputType.Console) { // Output to console logger.LogInformation(" Outputting results to console"); logger.LogInformation(reporter.Report(result)); } else { // Output to file var filename = Path.GetFileName(dOutput); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); logger.LogInformation($" Generating report '{report}'"); File.WriteAllText(report, reporter.Report(result)); } } var summary = new CoverageSummary(); var exceptionBuilder = new StringBuilder(); var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var thresholdFailed = false; var overallLineCoverage = summary.CalculateLineCoverage(result.Modules); var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules); var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules); foreach (var _module in result.Modules) { var linePercent = summary.CalculateLineCoverage(_module.Value).Percent * 100; var branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent * 100; var methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent * 100; coverageTable.AddRow(Path.GetFileNameWithoutExtension(_module.Key), $"{linePercent}%", $"{branchPercent}%", $"{methodPercent}%"); if (dThreshold > 0) { if (linePercent < dThreshold && dThresholdTypes.Contains("line")) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(_module.Key)}' has a line coverage '{linePercent}%' below specified threshold '{dThreshold}%'"); thresholdFailed = true; } if (branchPercent < dThreshold && dThresholdTypes.Contains("branch")) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(_module.Key)}' has a branch coverage '{branchPercent}%' below specified threshold '{dThreshold}%'"); thresholdFailed = true; } if (methodPercent < dThreshold && dThresholdTypes.Contains("method")) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(_module.Key)}' has a method coverage '{methodPercent}%' below specified threshold '{dThreshold}%'"); thresholdFailed = true; } } } logger.LogInformation(string.Empty); logger.LogInformation(coverageTable.ToStringAlternative()); logger.LogInformation($"Total Line: {overallLineCoverage.Percent * 100}%"); logger.LogInformation($"Total Branch: {overallBranchCoverage.Percent * 100}%"); logger.LogInformation($"Total Method: {overallMethodCoverage.Percent * 100}%"); if (thresholdFailed) { throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray())); } return(process.ExitCode == 0 ? 0 : process.ExitCode); }); try { return(app.Execute(args)); } catch (CommandParsingException ex) { logger.LogError(ex.Message); app.ShowHelp(); return(1); } catch (Exception ex) { logger.LogError(ex.Message); return(1); } }
static int Main(string[] args) { var logger = new ConsoleLogger(); var app = new CommandLineApplication(); app.Name = "coverlet"; app.FullName = "Cross platform .NET Core code coverage tool"; app.HelpOption("-h|--help"); app.VersionOption("-v|--version", GetAssemblyVersion()); int exitCode = (int)CommandExitCodes.Success; CommandArgument module = app.Argument("<ASSEMBLY>", "Path to the test assembly."); CommandOption target = app.Option("-t|--target", "Path to the test runner application.", CommandOptionType.SingleValue); CommandOption targs = app.Option("-a|--targetargs", "Arguments to be passed to the test runner.", CommandOptionType.SingleValue); CommandOption output = app.Option("-o|--output", "Output of the generated coverage report", CommandOptionType.SingleValue); CommandOption <LogLevel> verbosity = app.Option <LogLevel>("-v|--verbosity", "Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed.", CommandOptionType.SingleValue); CommandOption formats = app.Option("-f|--format", "Format of the generated coverage report.", CommandOptionType.MultipleValue); CommandOption threshold = app.Option("--threshold", "Exits with error if the coverage % is below value.", CommandOptionType.SingleValue); CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", CommandOptionType.MultipleValue); CommandOption thresholdStat = app.Option("--threshold-stat", "Coverage statistic used to enforce the threshold value.", CommandOptionType.SingleValue); CommandOption excludeFilters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); CommandOption includeFilters = app.Option("--include", "Filter expressions to include only specific modules and types.", CommandOptionType.MultipleValue); CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); CommandOption includeDirectories = app.Option("--include-directory", "Include directories containing additional assemblies to be instrumented.", CommandOptionType.MultipleValue); CommandOption excludeAttributes = app.Option("--exclude-by-attribute", "Attributes to exclude from code coverage.", CommandOptionType.MultipleValue); CommandOption includeTestAssembly = app.Option("--include-test-assembly", "Specifies whether to report code coverage of the test assembly.", CommandOptionType.NoValue); CommandOption singleHit = app.Option("--single-hit", "Specifies whether to limit code coverage hit reporting to a single hit for each location", CommandOptionType.NoValue); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue); app.OnExecute(() => { if (string.IsNullOrEmpty(module.Value) || string.IsNullOrWhiteSpace(module.Value)) { throw new CommandParsingException(app, "No test assembly specified."); } if (!target.HasValue()) { throw new CommandParsingException(app, "Target must be specified."); } if (verbosity.HasValue()) { // Adjust log level based on user input. logger.Level = verbosity.ParsedValue; } // We add default exclusion filter if no specified if (excludeFilters.Values.Count == 0) { excludeFilters.Values.Add("[xunit*]*"); } Coverage coverage = new Coverage(module.Value, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.Values.ToArray(), includeTestAssembly.HasValue(), singleHit.HasValue(), mergeWith.Value(), useSourceLink.HasValue(), logger); coverage.PrepareModules(); Process process = new Process(); process.StartInfo.FileName = target.Value(); process.StartInfo.Arguments = targs.HasValue() ? targs.Value() : string.Empty; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.OutputDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { logger.LogInformation(eventArgs.Data, important: true); } }; process.ErrorDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { logger.LogError(eventArgs.Data); } }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); process.WaitForExit(); var dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); var dThreshold = threshold.HasValue() ? double.Parse(threshold.Value()) : 0; var dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List <string>(new string[] { "line", "branch", "method" }); var dThresholdStat = thresholdStat.HasValue() ? Enum.Parse <ThresholdStatistic>(thresholdStat.Value(), true) : Enum.Parse <ThresholdStatistic>("minimum", true); logger.LogInformation("\nCalculating coverage result..."); var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(dOutput); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } foreach (var format in (formats.HasValue() ? formats.Values : new List <string>(new string[] { "json" }))) { var reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); } if (reporter.OutputType == ReporterOutputType.Console) { // Output to console logger.LogInformation(" Outputting results to console", important: true); logger.LogInformation(reporter.Report(result), important: true); } else { // Output to file var filename = Path.GetFileName(dOutput); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); logger.LogInformation($" Generating report '{report}'", important: true); File.WriteAllText(report, reporter.Report(result)); } } var thresholdTypeFlags = ThresholdTypeFlags.None; foreach (var thresholdType in dThresholdTypes) { if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } else if (thresholdType.Equals("branch", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } else if (thresholdType.Equals("method", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); int numModules = result.Modules.Count; var totalLinePercent = summary.CalculateLineCoverage(result.Modules).Percent; var totalBranchPercent = summary.CalculateBranchCoverage(result.Modules).Percent; var totalMethodPercent = summary.CalculateMethodCoverage(result.Modules).Percent; foreach (var _module in result.Modules) { var linePercent = summary.CalculateLineCoverage(_module.Value).Percent; var branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent; var methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent; coverageTable.AddRow(Path.GetFileNameWithoutExtension(_module.Key), $"{linePercent}%", $"{branchPercent}%", $"{methodPercent}%"); } logger.LogInformation(coverageTable.ToStringAlternative()); coverageTable.Columns.Clear(); coverageTable.Rows.Clear(); coverageTable.AddColumn(new[] { "", "Line", "Branch", "Method" }); coverageTable.AddRow("Total", $"{totalLinePercent}%", $"{totalBranchPercent}%", $"{totalMethodPercent}%"); coverageTable.AddRow("Average", $"{totalLinePercent / numModules}%", $"{totalBranchPercent / numModules}%", $"{totalMethodPercent / numModules}%"); logger.LogInformation(coverageTable.ToStringAlternative()); if (process.ExitCode > 0) { exitCode += (int)CommandExitCodes.TestFailed; } thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, dThreshold, thresholdTypeFlags, dThresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { exitCode += (int)CommandExitCodes.CoverageBelowThreshold; var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} line coverage is below the specified {dThreshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} branch coverage is below the specified {dThreshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {dThreshold}"); } throw new Exception(exceptionMessageBuilder.ToString()); } return(exitCode); }); try { return(app.Execute(args)); } catch (CommandParsingException ex) { logger.LogError(ex.Message); app.ShowHelp(); return((int)CommandExitCodes.CommandParsingException); } catch (Exception ex) { logger.LogError(ex.Message); return(exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception); } }
/// <summary> /// Prepares modules for getting coverage. /// Wrapper over coverage.PrepareModules /// </summary> /// <param name="coverage"></param> public void PrepareModules(Coverage coverage) { coverage.PrepareModules(); }
public override bool Execute() { AttachDebugger(); IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddTransient <IProcessExitHandler, ProcessExitHandler>(); serviceCollection.AddTransient <IFileSystem, FileSystem>(); serviceCollection.AddTransient <IConsole, SystemConsole>(); serviceCollection.AddTransient <ILogger, MSBuildLogger>(_ => _logger); serviceCollection.AddTransient <IRetryHelper, RetryHelper>(); // We cache resolutions serviceCollection.AddSingleton <ISourceRootTranslator, SourceRootTranslator>(serviceProvider => new SourceRootTranslator(Path, serviceProvider.GetRequiredService <ILogger>(), serviceProvider.GetRequiredService <IFileSystem>())); // We need to keep singleton/static semantics serviceCollection.AddSingleton <IInstrumentationHelper, InstrumentationHelper>(); serviceCollection.AddSingleton <ICecilSymbolHelper, CecilSymbolHelper>(); ServiceProvider = serviceCollection.BuildServiceProvider(); try { IFileSystem fileSystem = ServiceProvider.GetService <IFileSystem>(); var parameters = new CoverageParameters { IncludeFilters = Include?.Split(','), IncludeDirectories = IncludeDirectory?.Split(','), ExcludeFilters = Exclude?.Split(','), ExcludedSourceFiles = ExcludeByFile?.Split(','), ExcludeAttributes = ExcludeByAttribute?.Split(','), IncludeTestAssembly = IncludeTestAssembly, SingleHit = SingleHit, MergeWith = MergeWith, UseSourceLink = UseSourceLink, SkipAutoProps = SkipAutoProps, DeterministicReport = DeterministicReport, DoesNotReturnAttributes = DoesNotReturnAttribute?.Split(',') }; var coverage = new Coverage(Path, parameters, _logger, ServiceProvider.GetService <IInstrumentationHelper>(), ServiceProvider.GetService <IFileSystem>(), ServiceProvider.GetService <ISourceRootTranslator>(), ServiceProvider.GetService <ICecilSymbolHelper>()); CoveragePrepareResult prepareResult = coverage.PrepareModules(); InstrumenterState = new TaskItem(System.IO.Path.GetTempFileName()); using Stream instrumentedStateFile = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open, FileAccess.Write); using Stream serializedState = CoveragePrepareResult.Serialize(prepareResult); serializedState.CopyTo(instrumentedStateFile); } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }