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 linePercentCalculation = summary.CalculateLineCoverage(result.Modules); var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); var methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); var totalLinePercent = linePercentCalculation.Percent; var totalBranchPercent = branchPercentCalculation.Percent; var totalMethodPercent = methodPercentCalculation.Percent; var averageLinePercent = linePercentCalculation.AverageModulePercent; var averageBranchPercent = branchPercentCalculation.AverageModulePercent; var averageMethodPercent = methodPercentCalculation.AverageModulePercent; 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", $"{averageLinePercent}%", $"{averageBranchPercent}%", $"{averageMethodPercent}%"); 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); } }
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 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 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, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.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 == 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"); 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); } }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); IFileSystem fileSystem = ServiceProvider.GetService <IFileSystem>(); if (InstrumenterState is null || !fileSystem.Exists(InstrumenterState.ItemSpec)) { _logger.LogError("Result of instrumentation task not found"); return(false); } Coverage coverage = null; using (Stream instrumenterStateStream = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open)) { IInstrumentationHelper instrumentationHelper = ServiceProvider.GetService <IInstrumentationHelper>(); // Task.Log is teared down after a task and thus the new MSBuildLogger must be passed to the InstrumentationHelper // https://github.com/microsoft/msbuild/issues/5153 instrumentationHelper.SetLogger(_logger); coverage = new Coverage(CoveragePrepareResult.Deserialize(instrumenterStateStream), _logger, ServiceProvider.GetService <IInstrumentationHelper>(), fileSystem, ServiceProvider.GetService <ISourceRootTranslator>()); } try { fileSystem.Delete(InstrumenterState.ItemSpec); } catch (Exception ex) { // We don't want to block coverage for I/O errors _logger.LogWarning($"Exception during instrument state deletion, file name '{InstrumenterState.ItemSpec}' exception message '{ex.Message}'"); } CoverageResult result = coverage.GetCoverageResult(); string directory = Path.GetDirectoryName(Output); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } string[] formats = OutputFormat.Split(','); var coverageReportPaths = new List <ITaskItem>(formats.Length); ISourceRootTranslator sourceRootTranslator = ServiceProvider.GetService <ISourceRootTranslator>(); foreach (string format in formats) { IReporter 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 Console.WriteLine(" Outputting results to console"); Console.WriteLine(reporter.Report(result, sourceRootTranslator)); } else { ReportWriter writer = new(CoverletMultiTargetFrameworksCurrentTFM, directory, Output, reporter, fileSystem, ServiceProvider.GetService <IConsole>(), result, sourceRootTranslator); string path = writer.WriteReport(); var metadata = new Dictionary <string, string> { ["Format"] = format }; coverageReportPaths.Add(new TaskItem(path, metadata)); } } ReportItems = coverageReportPaths.ToArray(); var thresholdTypeFlagQueue = new Queue <ThresholdTypeFlags>(); foreach (string thresholdType in ThresholdType.Split(',').Select(t => t.Trim())) { if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Line); } else if (thresholdType.Equals("branch", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Branch); } else if (thresholdType.Equals("method", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Method); } } var thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>(); if (Threshold.Contains(',')) { IEnumerable <string> thresholdValues = Threshold.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); if (thresholdValues.Count() != thresholdTypeFlagQueue.Count) { throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count}) and values count ({thresholdValues.Count()}) doesn't match"); } foreach (string threshold in thresholdValues) { if (double.TryParse(threshold, out double value)) { thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = value; } else { throw new Exception($"Invalid threshold value must be numeric"); } } } else { double thresholdValue = double.Parse(Threshold); while (thresholdTypeFlagQueue.Any()) { thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = thresholdValue; } } ThresholdStatistic thresholdStat = ThresholdStatistic.Minimum; if (ThresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Average; } else if (ThresholdStat.Equals("total", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Total; } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); CoverageDetails linePercentCalculation = summary.CalculateLineCoverage(result.Modules); CoverageDetails branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); CoverageDetails methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); double totalLinePercent = linePercentCalculation.Percent; double totalBranchPercent = branchPercentCalculation.Percent; double totalMethodPercent = methodPercentCalculation.Percent; double averageLinePercent = linePercentCalculation.AverageModulePercent; double averageBranchPercent = branchPercentCalculation.AverageModulePercent; double averageMethodPercent = methodPercentCalculation.AverageModulePercent; foreach (KeyValuePair <string, Documents> module in result.Modules) { double linePercent = summary.CalculateLineCoverage(module.Value).Percent; double branchPercent = summary.CalculateBranchCoverage(module.Value).Percent; double methodPercent = summary.CalculateMethodCoverage(module.Value).Percent; coverageTable.AddRow(Path.GetFileNameWithoutExtension(module.Key), $"{InvariantFormat(linePercent)}%", $"{InvariantFormat(branchPercent)}%", $"{InvariantFormat(methodPercent)}%"); } Console.WriteLine(); Console.WriteLine(coverageTable.ToStringAlternative()); coverageTable.Columns.Clear(); coverageTable.Rows.Clear(); coverageTable.AddColumn(new[] { "", "Line", "Branch", "Method" }); coverageTable.AddRow("Total", $"{InvariantFormat(totalLinePercent)}%", $"{InvariantFormat(totalBranchPercent)}%", $"{InvariantFormat(totalMethodPercent)}%"); coverageTable.AddRow("Average", $"{InvariantFormat(averageLinePercent)}%", $"{InvariantFormat(averageBranchPercent)}%", $"{InvariantFormat(averageMethodPercent)}%"); Console.WriteLine(coverageTable.ToStringAlternative()); ThresholdTypeFlags thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine( $"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine( $"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Branch]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine( $"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}"); } throw new Exception(exceptionMessageBuilder.ToString()); } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }
public string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); XDocument xml = new XDocument(); XElement coverage = new XElement("CoverageSession"); XElement coverageSummary = new XElement("Summary"); XElement modules = new XElement("Modules"); int numClasses = 0, numMethods = 0; int visitedClasses = 0, visitedMethods = 0; int i = 1; foreach (var mod in result.Modules) { XElement module = new XElement("Module"); module.Add(new XAttribute("hash", Guid.NewGuid().ToString().ToUpper())); XElement path = new XElement("ModulePath", mod.Key); XElement time = new XElement("ModuleTime", DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ss")); XElement name = new XElement("ModuleName", Path.GetFileNameWithoutExtension(mod.Key)); module.Add(path); module.Add(time); module.Add(name); XElement files = new XElement("Files"); XElement classes = new XElement("Classes"); foreach (var doc in mod.Value) { XElement file = new XElement("File"); file.Add(new XAttribute("uid", i.ToString())); file.Add(new XAttribute("fullPath", doc.Key)); files.Add(file); foreach (var cls in doc.Value) { XElement @class = new XElement("Class"); XElement classSummary = new XElement("Summary"); XElement className = new XElement("FullName", cls.Key); XElement methods = new XElement("Methods"); int j = 0; var classVisited = false; foreach (var meth in cls.Value) { // Skip all methods with no lines if (meth.Value.Lines.Count == 0) { continue; } var methLineCoverage = summary.CalculateLineCoverage(meth.Value.Lines); var methBranchCoverage = summary.CalculateBranchCoverage(meth.Value.Branches); XElement method = new XElement("Method"); method.Add(new XAttribute("cyclomaticComplexity", "0")); method.Add(new XAttribute("nPathComplexity", "0")); method.Add(new XAttribute("sequenceCoverage", methLineCoverage.Percent.ToString())); method.Add(new XAttribute("branchCoverage", methBranchCoverage.Percent.ToString())); method.Add(new XAttribute("isConstructor", meth.Key.Contains("ctor").ToString())); method.Add(new XAttribute("isGetter", meth.Key.Contains("get_").ToString())); method.Add(new XAttribute("isSetter", meth.Key.Contains("set_").ToString())); method.Add(new XAttribute("isStatic", (!meth.Key.Contains("get_") || !meth.Key.Contains("set_")).ToString())); XElement methodName = new XElement("Name", meth.Key); XElement fileRef = new XElement("FileRef"); fileRef.Add(new XAttribute("uid", i.ToString())); XElement methodPoint = new XElement("MethodPoint"); methodPoint.Add(new XAttribute("vc", methLineCoverage.Covered.ToString())); methodPoint.Add(new XAttribute("upsid", "0")); methodPoint.Add(new XAttribute(XName.Get("type", "xsi"), "SequencePoint")); methodPoint.Add(new XAttribute("ordinal", j.ToString())); methodPoint.Add(new XAttribute("offset", j.ToString())); methodPoint.Add(new XAttribute("sc", "0")); methodPoint.Add(new XAttribute("sl", meth.Value.Lines.First().Key.ToString())); methodPoint.Add(new XAttribute("ec", "1")); methodPoint.Add(new XAttribute("el", meth.Value.Lines.Last().Key.ToString())); methodPoint.Add(new XAttribute("bec", "0")); methodPoint.Add(new XAttribute("bev", "0")); methodPoint.Add(new XAttribute("fileid", i.ToString())); // They're really just lines XElement sequencePoints = new XElement("SequencePoints"); XElement branchPoints = new XElement("BranchPoints"); XElement methodSummary = new XElement("Summary"); int k = 0; int kBr = 0; var methodVisited = false; foreach (var lines in meth.Value.Lines) { XElement sequencePoint = new XElement("SequencePoint"); sequencePoint.Add(new XAttribute("vc", lines.Value.ToString())); sequencePoint.Add(new XAttribute("upsid", lines.Key.ToString())); sequencePoint.Add(new XAttribute("ordinal", k.ToString())); sequencePoint.Add(new XAttribute("sl", lines.Key.ToString())); sequencePoint.Add(new XAttribute("sc", "1")); sequencePoint.Add(new XAttribute("el", lines.Key.ToString())); sequencePoint.Add(new XAttribute("ec", "2")); sequencePoint.Add(new XAttribute("bec", "0")); sequencePoint.Add(new XAttribute("bev", "0")); sequencePoint.Add(new XAttribute("fileid", i.ToString())); sequencePoints.Add(sequencePoint); if (lines.Value > 0) { classVisited = true; methodVisited = true; } k++; } foreach (var branche in meth.Value.Branches) { XElement branchPoint = new XElement("BranchPoint"); branchPoint.Add(new XAttribute("vc", branche.Hits.ToString())); branchPoint.Add(new XAttribute("upsid", branche.Line.ToString())); branchPoint.Add(new XAttribute("ordinal", branche.Ordinal.ToString())); branchPoint.Add(new XAttribute("path", branche.Path.ToString())); branchPoint.Add(new XAttribute("offset", branche.Offset.ToString())); branchPoint.Add(new XAttribute("offsetend", branche.EndOffset.ToString())); branchPoint.Add(new XAttribute("sl", branche.Line.ToString())); branchPoint.Add(new XAttribute("fileid", i.ToString())); branchPoints.Add(branchPoint); kBr++; } numMethods++; if (methodVisited) { visitedMethods++; } methodSummary.Add(new XAttribute("numSequencePoints", methLineCoverage.Total.ToString())); methodSummary.Add(new XAttribute("visitedSequencePoints", methLineCoverage.Covered.ToString())); methodSummary.Add(new XAttribute("numBranchPoints", methBranchCoverage.Total.ToString())); methodSummary.Add(new XAttribute("visitedBranchPoints", methBranchCoverage.Covered.ToString())); methodSummary.Add(new XAttribute("sequenceCoverage", methLineCoverage.Percent.ToString())); methodSummary.Add(new XAttribute("branchCoverage", methBranchCoverage.Percent.ToString())); methodSummary.Add(new XAttribute("maxCyclomaticComplexity", "0")); methodSummary.Add(new XAttribute("minCyclomaticComplexity", "0")); methodSummary.Add(new XAttribute("visitedClasses", "0")); methodSummary.Add(new XAttribute("numClasses", "0")); methodSummary.Add(new XAttribute("visitedMethods", methodVisited ? "1" : "0")); methodSummary.Add(new XAttribute("numMethods", "1")); method.Add(methodSummary); method.Add(new XElement("MetadataToken")); method.Add(methodName); method.Add(fileRef); method.Add(sequencePoints); method.Add(branchPoints); method.Add(methodPoint); methods.Add(method); j++; } numClasses++; if (classVisited) { visitedClasses++; } var classLineCoverage = summary.CalculateLineCoverage(cls.Value); var classBranchCoverage = summary.CalculateBranchCoverage(cls.Value); var classMethodCoverage = summary.CalculateMethodCoverage(cls.Value); classSummary.Add(new XAttribute("numSequencePoints", classLineCoverage.Total.ToString())); classSummary.Add(new XAttribute("visitedSequencePoints", classLineCoverage.Covered.ToString())); classSummary.Add(new XAttribute("numBranchPoints", classBranchCoverage.Total.ToString())); classSummary.Add(new XAttribute("visitedBranchPoints", classBranchCoverage.Covered.ToString())); classSummary.Add(new XAttribute("sequenceCoverage", classLineCoverage.Percent.ToString())); classSummary.Add(new XAttribute("branchCoverage", classBranchCoverage.Percent.ToString())); classSummary.Add(new XAttribute("maxCyclomaticComplexity", "0")); classSummary.Add(new XAttribute("minCyclomaticComplexity", "0")); classSummary.Add(new XAttribute("visitedClasses", classVisited ? "1" : "0")); classSummary.Add(new XAttribute("numClasses", "1")); classSummary.Add(new XAttribute("visitedMethods", classMethodCoverage.Covered.ToString())); classSummary.Add(new XAttribute("numMethods", classMethodCoverage.Total.ToString())); @class.Add(classSummary); @class.Add(className); @class.Add(methods); classes.Add(@class); } i++; } module.Add(files); module.Add(classes); modules.Add(module); } var moduleLineCoverage = summary.CalculateLineCoverage(result.Modules); var moduleBranchCoverage = summary.CalculateLineCoverage(result.Modules); coverageSummary.Add(new XAttribute("numSequencePoints", moduleLineCoverage.Total.ToString())); coverageSummary.Add(new XAttribute("visitedSequencePoints", moduleLineCoverage.Covered.ToString())); coverageSummary.Add(new XAttribute("numBranchPoints", moduleBranchCoverage.Total.ToString())); coverageSummary.Add(new XAttribute("visitedBranchPoints", moduleBranchCoverage.Covered.ToString())); coverageSummary.Add(new XAttribute("sequenceCoverage", moduleLineCoverage.Percent.ToString())); coverageSummary.Add(new XAttribute("branchCoverage", moduleBranchCoverage.Percent.ToString())); coverageSummary.Add(new XAttribute("maxCyclomaticComplexity", "0")); coverageSummary.Add(new XAttribute("minCyclomaticComplexity", "0")); coverageSummary.Add(new XAttribute("visitedClasses", visitedClasses.ToString())); coverageSummary.Add(new XAttribute("numClasses", numClasses.ToString())); coverageSummary.Add(new XAttribute("visitedMethods", visitedMethods.ToString())); coverageSummary.Add(new XAttribute("numMethods", numMethods.ToString())); coverage.Add(coverageSummary); coverage.Add(modules); xml.Add(coverage); var stream = new MemoryStream(); xml.Save(stream); return(Encoding.UTF8.GetString(stream.ToArray())); }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); IFileSystem fileSystem = ServiceProvider.GetService <IFileSystem>(); if (InstrumenterState is null || !fileSystem.Exists(InstrumenterState.ItemSpec)) { _logger.LogError("Result of instrumentation task not found"); return(false); } Coverage coverage = null; using (Stream instrumenterStateStream = fileSystem.NewFileStream(InstrumenterState.ItemSpec, FileMode.Open)) { var instrumentationHelper = ServiceProvider.GetService <IInstrumentationHelper>(); // Task.Log is teared down after a task and thus the new MSBuildLogger must be passed to the InstrumentationHelper // https://github.com/microsoft/msbuild/issues/5153 instrumentationHelper.SetLogger(_logger); coverage = new Coverage(CoveragePrepareResult.Deserialize(instrumenterStateStream), this._logger, ServiceProvider.GetService <IInstrumentationHelper>(), fileSystem, ServiceProvider.GetService <ISourceRootTranslator>()); } try { fileSystem.Delete(InstrumenterState.ItemSpec); } catch (Exception ex) { // We don't want to block coverage for I/O errors _logger.LogWarning($"Exception during instrument state deletion, file name '{InstrumenterState.ItemSpec}' exception message '{ex.Message}'"); } CoverageResult result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_output); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var formats = _format.Split(','); foreach (var format in formats) { 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 Console.WriteLine(" Outputting results to console"); Console.WriteLine(reporter.Report(result)); } else { ReportWriter writer = new ReportWriter(_coverletMultiTargetFrameworksCurrentTFM, directory, _output, reporter, fileSystem, ServiceProvider.GetService <IConsole>(), result); writer.WriteReport(); } } var thresholdTypeFlags = ThresholdTypeFlags.None; var thresholdStat = ThresholdStatistic.Minimum; foreach (var thresholdType in _thresholdType.Split(',').Select(t => t.Trim())) { 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; } } if (_thresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Average; } else if (_thresholdStat.Equals("total", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Total; } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); int numModules = result.Modules.Count; var linePercentCalculation = summary.CalculateLineCoverage(result.Modules); var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); var methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); var totalLinePercent = linePercentCalculation.Percent; var totalBranchPercent = branchPercentCalculation.Percent; var totalMethodPercent = methodPercentCalculation.Percent; var averageLinePercent = linePercentCalculation.AverageModulePercent; var averageBranchPercent = branchPercentCalculation.AverageModulePercent; var averageMethodPercent = methodPercentCalculation.AverageModulePercent; 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}%"); } Console.WriteLine(); Console.WriteLine(coverageTable.ToStringAlternative()); coverageTable.Columns.Clear(); coverageTable.Rows.Clear(); coverageTable.AddColumn(new[] { "", "Line", "Branch", "Method" }); coverageTable.AddRow("Total", $"{totalLinePercent}%", $"{totalBranchPercent}%", $"{totalMethodPercent}%"); coverageTable.AddRow("Average", $"{averageLinePercent}%", $"{averageBranchPercent}%", $"{averageMethodPercent}%"); Console.WriteLine(coverageTable.ToStringAlternative()); thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, _threshold, thresholdTypeFlags, thresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {_threshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {_threshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {_threshold}"); } throw new Exception(exceptionMessageBuilder.ToString()); } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); var coverage = InstrumentationTask.Coverage; var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_output); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var formats = _format.Split(','); foreach (var format in formats) { var reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); } var filename = Path.GetFileName(_output); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); Console.WriteLine($" Generating report '{report}'"); File.WriteAllText(report, reporter.Report(result)); } var thresholdFailed = false; var thresholdTypes = _thresholdType.Split(',').Select(t => t.Trim()); var summary = new CoverageSummary(); var exceptionBuilder = new StringBuilder(); var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var overallLineCoverage = summary.CalculateLineCoverage(result.Modules).Percent * 100; var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules).Percent * 100; var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules).Percent * 100; 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 (_threshold > 0) { if (linePercent < _threshold && thresholdTypes.Contains("line", StringComparer.OrdinalIgnoreCase)) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(module.Key)}' has a line coverage '{linePercent}%' below specified threshold '{_threshold}%'"); thresholdFailed = true; } if (branchPercent < _threshold && thresholdTypes.Contains("branch", StringComparer.OrdinalIgnoreCase)) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(module.Key)}' has a branch coverage '{branchPercent}%' below specified threshold '{_threshold}%'"); thresholdFailed = true; } if (methodPercent < _threshold && thresholdTypes.Contains("method", StringComparer.OrdinalIgnoreCase)) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(module.Key)}' has a method coverage '{methodPercent}%' below specified threshold '{_threshold}%'"); thresholdFailed = true; } } } Console.WriteLine(); Console.WriteLine(coverageTable.ToStringAlternative()); Console.WriteLine($"Total Line: {overallLineCoverage}%"); Console.WriteLine($"Total Branch: {overallBranchCoverage}%"); Console.WriteLine($"Total Method: {overallMethodCoverage}%"); if (thresholdFailed) { throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray())); } } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); var lineCoverage = summary.CalculateLineCoverage(result.Modules); var branchCoverage = summary.CalculateBranchCoverage(result.Modules); XDocument xml = new XDocument(); XElement coverage = new XElement("coverage"); coverage.Add(new XAttribute("line-rate", summary.CalculateLineCoverage(result.Modules).Percent.ToString())); coverage.Add(new XAttribute("branch-rate", summary.CalculateBranchCoverage(result.Modules).Percent.ToString())); coverage.Add(new XAttribute("version", "1.9")); coverage.Add(new XAttribute("timestamp", ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString())); XElement sources = new XElement("sources"); XElement packages = new XElement("packages"); foreach (var module in result.Modules) { XElement package = new XElement("package"); package.Add(new XAttribute("name", Path.GetFileNameWithoutExtension(module.Key))); package.Add(new XAttribute("line-rate", summary.CalculateLineCoverage(module.Value).Percent.ToString())); package.Add(new XAttribute("branch-rate", summary.CalculateBranchCoverage(module.Value).Percent.ToString())); package.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(module.Value).ToString())); XElement classes = new XElement("classes"); foreach (var document in module.Value) { foreach (var cls in document.Value) { XElement @class = new XElement("class"); @class.Add(new XAttribute("name", cls.Key)); @class.Add(new XAttribute("filename", document.Key)); @class.Add(new XAttribute("line-rate", summary.CalculateLineCoverage(cls.Value).Percent.ToString())); @class.Add(new XAttribute("branch-rate", summary.CalculateBranchCoverage(cls.Value).Percent.ToString())); @class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value).ToString())); XElement classLines = new XElement("lines"); XElement methods = new XElement("methods"); foreach (var meth in cls.Value) { // Skip all methods with no lines if (meth.Value.Lines.Count == 0) { continue; } XElement method = new XElement("method"); method.Add(new XAttribute("name", meth.Key.Split(':')[2].Split('(')[0])); method.Add(new XAttribute("signature", "(" + meth.Key.Split(':')[2].Split('(')[1])); method.Add(new XAttribute("line-rate", summary.CalculateLineCoverage(meth.Value.Lines).Percent.ToString())); method.Add(new XAttribute("branch-rate", summary.CalculateBranchCoverage(meth.Value.Branches).Percent.ToString())); XElement lines = new XElement("lines"); foreach (var ln in meth.Value.Lines) { bool isBranchPoint = meth.Value.Branches.Any(b => b.Line == ln.Key); XElement line = new XElement("line"); line.Add(new XAttribute("number", ln.Key.ToString())); line.Add(new XAttribute("hits", ln.Value.ToString())); line.Add(new XAttribute("branch", isBranchPoint.ToString())); if (isBranchPoint) { var branches = meth.Value.Branches.Where(b => b.Line == ln.Key).ToList(); var branchInfoCoverage = summary.CalculateBranchCoverage(branches); line.Add(new XAttribute("condition-coverage", $"{branchInfoCoverage.Percent*100}% ({branchInfoCoverage.Covered}/{branchInfoCoverage.Total})")); XElement conditions = new XElement("conditions"); var byOffset = branches.GroupBy(b => b.Offset).ToDictionary(b => b.Key, b => b.ToList()); foreach (var entry in byOffset) { XElement condition = new XElement("condition"); 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("coverage", $"{summary.CalculateBranchCoverage(entry.Value).Percent * 100}%")); conditions.Add(condition); } line.Add(conditions); } lines.Add(line); classLines.Add(line); } method.Add(lines); methods.Add(method); } @class.Add(methods); @class.Add(classLines); classes.Add(@class); } } package.Add(classes); packages.Add(package); } coverage.Add(new XAttribute("lines-covered", lineCoverage.Covered.ToString())); coverage.Add(new XAttribute("lines-valid", lineCoverage.Total.ToString())); coverage.Add(new XAttribute("branches-covered", branchCoverage.Covered.ToString())); coverage.Add(new XAttribute("branches-valid", branchCoverage.Total.ToString())); coverage.Add(sources); coverage.Add(packages); xml.Add(coverage); var stream = new MemoryStream(); xml.Save(stream); return(Encoding.UTF8.GetString(stream.ToArray())); }
static int Main(string[] args) { IServiceCollection serviceCollection = new ServiceCollection(); serviceCollection.AddTransient <IRetryHelper, RetryHelper>(); serviceCollection.AddTransient <IProcessExitHandler, ProcessExitHandler>(); serviceCollection.AddTransient <IFileSystem, FileSystem>(); serviceCollection.AddTransient <ILogger, ConsoleLogger>(); // We need to keep singleton/static semantics serviceCollection.AddSingleton <IInstrumentationHelper, InstrumentationHelper>(); serviceCollection.AddSingleton <ISourceRootTranslator, SourceRootTranslator>(provider => new SourceRootTranslator(provider.GetRequiredService <ILogger>(), provider.GetRequiredService <IFileSystem>())); serviceCollection.AddSingleton <ICecilSymbolHelper, CecilSymbolHelper>(); ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); var logger = (ConsoleLogger)serviceProvider.GetService <ILogger>(); var fileSystem = serviceProvider.GetService <IFileSystem>(); var app = new CommandLineApplication { Name = "coverlet", FullName = "Cross platform .NET Core code coverage tool" }; app.HelpOption("-h|--help"); app.VersionOption("-v|--version", GetAssemblyVersion()); int exitCode = (int)CommandExitCodes.Success; CommandArgument moduleOrAppDirectory = app.Argument("<ASSEMBLY|DIRECTORY>", "Path to the test assembly or application directory."); 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 skipAutoProp = app.Option("--skipautoprops", "Neither track nor record auto-implemented properties.", CommandOptionType.NoValue); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue); CommandOption doesNotReturnAttributes = app.Option("--does-not-return-attribute", "Attributes that mark methods that do not return.", CommandOptionType.MultipleValue); app.OnExecute(() => { if (string.IsNullOrEmpty(moduleOrAppDirectory.Value) || string.IsNullOrWhiteSpace(moduleOrAppDirectory.Value)) { throw new CommandParsingException(app, "No test assembly or application directory 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; } CoverageParameters parameters = new() { IncludeFilters = includeFilters.Values.ToArray(), IncludeDirectories = includeDirectories.Values.ToArray(), ExcludeFilters = excludeFilters.Values.ToArray(), ExcludedSourceFiles = excludedSourceFiles.Values.ToArray(), ExcludeAttributes = excludeAttributes.Values.ToArray(), IncludeTestAssembly = includeTestAssembly.HasValue(), SingleHit = singleHit.HasValue(), MergeWith = mergeWith.Value(), UseSourceLink = useSourceLink.HasValue(), SkipAutoProps = skipAutoProp.HasValue(), DoesNotReturnAttributes = doesNotReturnAttributes.Values.ToArray() }; ISourceRootTranslator sourceRootTranslator = serviceProvider.GetRequiredService <ISourceRootTranslator>(); Coverage coverage = new(moduleOrAppDirectory.Value, parameters, logger, serviceProvider.GetRequiredService <IInstrumentationHelper>(), fileSystem, sourceRootTranslator, serviceProvider.GetRequiredService <ICecilSymbolHelper>()); coverage.PrepareModules(); Process process = new(); 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 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, sourceRootTranslator), 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); fileSystem.WriteAllText(report, reporter.Report(result, sourceRootTranslator)); } } var thresholdTypeFlagQueue = new Queue <ThresholdTypeFlags>(); foreach (var thresholdType in dThresholdTypes) { if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Line); } else if (thresholdType.Equals("branch", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Branch); } else if (thresholdType.Equals("method", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlagQueue.Enqueue(ThresholdTypeFlags.Method); } } Dictionary <ThresholdTypeFlags, double> thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>(); if (threshold.HasValue() && threshold.Value().Contains(',')) { var thresholdValues = threshold.Value().Split(',', StringSplitOptions.RemoveEmptyEntries).Select(t => t.Trim()); if (thresholdValues.Count() != thresholdTypeFlagQueue.Count()) { throw new Exception($"Threshold type flag count ({thresholdTypeFlagQueue.Count()}) and values count ({thresholdValues.Count()}) doesn't match"); } foreach (var thresholdValue in thresholdValues) { if (double.TryParse(thresholdValue, out var value)) { thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = value; } else { throw new Exception($"Invalid threshold value must be numeric"); } } } else { double thresholdValue = threshold.HasValue() ? double.Parse(threshold.Value()) : 0; while (thresholdTypeFlagQueue.Any()) { thresholdTypeFlagValues[thresholdTypeFlagQueue.Dequeue()] = thresholdValue; } } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); var linePercentCalculation = summary.CalculateLineCoverage(result.Modules); var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); var methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); var totalLinePercent = linePercentCalculation.Percent; var totalBranchPercent = branchPercentCalculation.Percent; var totalMethodPercent = methodPercentCalculation.Percent; var averageLinePercent = linePercentCalculation.AverageModulePercent; var averageBranchPercent = branchPercentCalculation.AverageModulePercent; var averageMethodPercent = methodPercentCalculation.AverageModulePercent; 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", $"{averageLinePercent}%", $"{averageBranchPercent}%", $"{averageMethodPercent}%"); logger.LogInformation(coverageTable.ToStringAlternative()); if (process.ExitCode > 0) { exitCode += (int)CommandExitCodes.TestFailed; } var thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, 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 {thresholdTypeFlagValues[ThresholdTypeFlags.Line]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} branch coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Branch]}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {thresholdTypeFlagValues[ThresholdTypeFlags.Method]}"); } 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 (Win32Exception we) when(we.Source == "System.Diagnostics.Process") { logger.LogError($"Start process '{target.Value()}' failed with '{we.Message}'"); return(exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception); } catch (Exception ex) { logger.LogError(ex.Message); return(exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception); } }
public string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); XmlDocument xml = new XmlDocument(); XmlElement coverage = xml.CreateElement("CoverageSession"); XmlElement coverageSummary = xml.CreateElement("Summary"); XmlElement modules = xml.CreateElement("Modules"); int numSequencePoints = 0, numBranchPoints = 0, numClasses = 0, numMethods = 0; int visitedSequencePoints = 0, visitedBranchPoints = 0, visitedClasses = 0, visitedMethods = 0; int i = 1; foreach (var mod in result.Modules) { XmlElement module = xml.CreateElement("Module"); module.SetAttribute("hash", Guid.NewGuid().ToString().ToUpper()); XmlElement path = xml.CreateElement("ModulePath"); path.AppendChild(xml.CreateTextNode(mod.Key)); XmlElement time = xml.CreateElement("ModuleTime"); time.AppendChild(xml.CreateTextNode(DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ss"))); XmlElement name = xml.CreateElement("ModuleName"); name.AppendChild(xml.CreateTextNode(Path.GetFileNameWithoutExtension(mod.Key))); module.AppendChild(path); module.AppendChild(time); module.AppendChild(name); XmlElement files = xml.CreateElement("Files"); XmlElement classes = xml.CreateElement("Classes"); foreach (var doc in mod.Value) { XmlElement file = xml.CreateElement("File"); file.SetAttribute("uid", i.ToString()); file.SetAttribute("fullPath", doc.Key); files.AppendChild(file); foreach (var cls in doc.Value) { XmlElement @class = xml.CreateElement("Class"); XmlElement classSummary = xml.CreateElement("Summary"); XmlElement className = xml.CreateElement("FullName"); className.AppendChild(xml.CreateTextNode(cls.Key)); XmlElement methods = xml.CreateElement("Methods"); int j = 0; var classVisited = false; foreach (var meth in cls.Value) { XmlElement method = xml.CreateElement("Method"); method.SetAttribute("cyclomaticComplexity", "0"); method.SetAttribute("nPathComplexity", "0"); method.SetAttribute("sequenceCoverage", summary.CalculateLineCoverage(meth.Value).ToString()); method.SetAttribute("branchCoverage", summary.CalculateBranchCoverage(meth.Value).ToString()); method.SetAttribute("isConstructor", meth.Key.Contains("ctor").ToString()); method.SetAttribute("isGetter", meth.Key.Contains("get_").ToString()); method.SetAttribute("isSetter", meth.Key.Contains("set_").ToString()); method.SetAttribute("isStatic", (!meth.Key.Contains("get_") || !meth.Key.Contains("set_")).ToString()); XmlElement methodName = xml.CreateElement("Name"); methodName.AppendChild(xml.CreateTextNode(meth.Key)); XmlElement fileRef = xml.CreateElement("FileRef"); fileRef.SetAttribute("uid", i.ToString()); XmlElement methodPoint = xml.CreateElement("MethodPoint"); methodPoint.SetAttribute("vc", meth.Value.Select(l => l.Value.Hits).Sum().ToString()); methodPoint.SetAttribute("upsid", "0"); methodPoint.SetAttribute("type", "xsi", "SequencePoint"); methodPoint.SetAttribute("ordinal", j.ToString()); methodPoint.SetAttribute("offset", j.ToString()); methodPoint.SetAttribute("sc", "0"); methodPoint.SetAttribute("sl", meth.Value.First().Key.ToString()); methodPoint.SetAttribute("ec", "1"); methodPoint.SetAttribute("el", meth.Value.Last().Key.ToString()); methodPoint.SetAttribute("bec", "0"); methodPoint.SetAttribute("bev", "0"); methodPoint.SetAttribute("fileid", i.ToString()); // They're really just lines XmlElement sequencePoints = xml.CreateElement("SequencePoints"); XmlElement branchPoints = xml.CreateElement("BranchPoints"); XmlElement methodSummary = xml.CreateElement("Summary"); int k = 0; int kBr = 0; var methodVisited = false; foreach (var lines in meth.Value) { XmlElement sequencePoint = xml.CreateElement("SequencePoint"); sequencePoint.SetAttribute("vc", lines.Value.Hits.ToString()); sequencePoint.SetAttribute("upsid", lines.Key.ToString()); sequencePoint.SetAttribute("ordinal", k.ToString()); sequencePoint.SetAttribute("sl", lines.Key.ToString()); sequencePoint.SetAttribute("sc", "1"); sequencePoint.SetAttribute("el", lines.Key.ToString()); sequencePoint.SetAttribute("ec", "2"); sequencePoint.SetAttribute("bec", "0"); sequencePoint.SetAttribute("bev", "0"); sequencePoint.SetAttribute("fileid", i.ToString()); sequencePoints.AppendChild(sequencePoint); if (lines.Value.IsBranchPoint) { XmlElement branchPoint = xml.CreateElement("BranchPoint"); branchPoint.SetAttribute("vc", lines.Value.Hits.ToString()); branchPoint.SetAttribute("upsid", lines.Key.ToString()); branchPoint.SetAttribute("ordinal", kBr.ToString()); branchPoint.SetAttribute("sl", lines.Key.ToString()); branchPoint.SetAttribute("fileid", i.ToString()); branchPoints.AppendChild(branchPoint); kBr++; numBranchPoints++; } numSequencePoints++; if (lines.Value.Hits > 0) { visitedSequencePoints++; classVisited = true; methodVisited = true; if (lines.Value.IsBranchPoint) { visitedBranchPoints++; } } k++; } numMethods++; if (methodVisited) { visitedMethods++; } methodSummary.SetAttribute("numSequencePoints", meth.Value.Count().ToString()); methodSummary.SetAttribute("visitedSequencePoints", meth.Value.Where(l => l.Value.Hits > 0).Count().ToString()); methodSummary.SetAttribute("numBranchPoints", meth.Value.Where(l => l.Value.IsBranchPoint).Count().ToString()); methodSummary.SetAttribute("visitedBranchPoints", meth.Value.Where(l => l.Value.IsBranchPoint && l.Value.Hits > 0).Count().ToString()); methodSummary.SetAttribute("sequenceCoverage", summary.CalculateLineCoverage(meth.Value).ToString()); methodSummary.SetAttribute("branchCoverage", summary.CalculateBranchCoverage(meth.Value).ToString()); methodSummary.SetAttribute("maxCyclomaticComplexity", "0"); methodSummary.SetAttribute("minCyclomaticComplexity", "0"); methodSummary.SetAttribute("visitedClasses", "0"); methodSummary.SetAttribute("numClasses", "0"); methodSummary.SetAttribute("visitedMethods", methodVisited ? "1" : "0"); methodSummary.SetAttribute("numMethods", "1"); method.AppendChild(methodSummary); method.AppendChild(xml.CreateElement("MetadataToken")); method.AppendChild(methodName); method.AppendChild(fileRef); method.AppendChild(sequencePoints); method.AppendChild(branchPoints); method.AppendChild(methodPoint); methods.AppendChild(method); j++; } numClasses++; if (classVisited) { visitedClasses++; } classSummary.SetAttribute("numSequencePoints", cls.Value.Select(c => c.Value.Count).Sum().ToString()); classSummary.SetAttribute("visitedSequencePoints", cls.Value.Select(c => c.Value.Where(l => l.Value.Hits > 0).Count()).Sum().ToString()); classSummary.SetAttribute("numBranchPoints", cls.Value.Select(c => c.Value.Count(l => l.Value.IsBranchPoint)).Sum().ToString()); classSummary.SetAttribute("visitedBranchPoints", cls.Value.Select(c => c.Value.Where(l => l.Value.Hits > 0 && l.Value.IsBranchPoint).Count()).Sum().ToString()); classSummary.SetAttribute("sequenceCoverage", summary.CalculateLineCoverage(cls.Value).ToString()); classSummary.SetAttribute("branchCoverage", summary.CalculateBranchCoverage(cls.Value).ToString()); classSummary.SetAttribute("maxCyclomaticComplexity", "0"); classSummary.SetAttribute("minCyclomaticComplexity", "0"); classSummary.SetAttribute("visitedClasses", classVisited ? "1" : "0"); classSummary.SetAttribute("numClasses", "1"); classSummary.SetAttribute("visitedMethods", "0"); classSummary.SetAttribute("numMethods", cls.Value.Count.ToString()); @class.AppendChild(classSummary); @class.AppendChild(className); @class.AppendChild(methods); classes.AppendChild(@class); } i++; } module.AppendChild(files); module.AppendChild(classes); modules.AppendChild(module); } coverageSummary.SetAttribute("numSequencePoints", numSequencePoints.ToString()); coverageSummary.SetAttribute("visitedSequencePoints", visitedSequencePoints.ToString()); coverageSummary.SetAttribute("numBranchPoints", numBranchPoints.ToString()); coverageSummary.SetAttribute("visitedBranchPoints", visitedBranchPoints.ToString()); coverageSummary.SetAttribute("sequenceCoverage", summary.CalculateLineCoverage(result.Modules).ToString()); coverageSummary.SetAttribute("branchCoverage", summary.CalculateLineCoverage(result.Modules).ToString()); coverageSummary.SetAttribute("maxCyclomaticComplexity", "0"); coverageSummary.SetAttribute("minCyclomaticComplexity", "0"); coverageSummary.SetAttribute("visitedClasses", visitedClasses.ToString()); coverageSummary.SetAttribute("numClasses", numClasses.ToString()); coverageSummary.SetAttribute("visitedMethods", visitedMethods.ToString()); coverageSummary.SetAttribute("numMethods", numMethods.ToString()); coverage.AppendChild(coverageSummary); coverage.AppendChild(modules); xml.AppendChild(coverage); Utf8StringWriter writer = new Utf8StringWriter(); xml.Save(writer); return(writer.ToString()); }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); if (InstrumenterState is null || !File.Exists(InstrumenterState.ItemSpec)) { _logger.LogError("Result of instrumentation task not found"); return(false); } var coverage = new Coverage(CoveragePrepareResult.Deserialize(new FileStream(InstrumenterState.ItemSpec, FileMode.Open)), this._logger, (IInstrumentationHelper)DependencyInjection.Current.GetService(typeof(IInstrumentationHelper))); var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_output); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var formats = _format.Split(','); foreach (var format in formats) { 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 Console.WriteLine(" Outputting results to console"); Console.WriteLine(reporter.Report(result)); } else { // Output to file var filename = Path.GetFileName(_output); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); Console.WriteLine($" Generating report '{report}'"); File.WriteAllText(report, reporter.Report(result)); } } var thresholdTypeFlags = ThresholdTypeFlags.None; var thresholdStat = ThresholdStatistic.Minimum; foreach (var thresholdType in _thresholdType.Split(',').Select(t => t.Trim())) { 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; } } if (_thresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Average; } else if (_thresholdStat.Equals("total", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Total; } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); int numModules = result.Modules.Count; var linePercentCalculation = summary.CalculateLineCoverage(result.Modules); var branchPercentCalculation = summary.CalculateBranchCoverage(result.Modules); var methodPercentCalculation = summary.CalculateMethodCoverage(result.Modules); var totalLinePercent = linePercentCalculation.Percent; var totalBranchPercent = branchPercentCalculation.Percent; var totalMethodPercent = methodPercentCalculation.Percent; var averageLinePercent = linePercentCalculation.AverageModulePercent; var averageBranchPercent = branchPercentCalculation.AverageModulePercent; var averageMethodPercent = methodPercentCalculation.AverageModulePercent; 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}%"); } Console.WriteLine(); Console.WriteLine(coverageTable.ToStringAlternative()); coverageTable.Columns.Clear(); coverageTable.Rows.Clear(); coverageTable.AddColumn(new[] { "", "Line", "Branch", "Method" }); coverageTable.AddRow("Total", $"{totalLinePercent}%", $"{totalBranchPercent}%", $"{totalMethodPercent}%"); coverageTable.AddRow("Average", $"{averageLinePercent}%", $"{averageBranchPercent}%", $"{averageMethodPercent}%"); Console.WriteLine(coverageTable.ToStringAlternative()); thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, _threshold, thresholdTypeFlags, thresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {_threshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {_threshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {_threshold}"); } throw new Exception(exceptionMessageBuilder.ToString()); } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }
public string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); XmlDocument xml = new XmlDocument(); XmlElement coverage = xml.CreateElement("coverage"); coverage.SetAttribute("line-rate", summary.CalculateLineCoverage(result.Modules).ToString()); coverage.SetAttribute("branch-rate", summary.CalculateBranchCoverage(result.Modules).ToString()); coverage.SetAttribute("version", "1.9"); coverage.SetAttribute("timestamp", ((int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds).ToString()); XmlElement sources = xml.CreateElement("sources"); foreach (var src in GetSources(result.Modules)) { XmlElement source = xml.CreateElement("source"); source.AppendChild(xml.CreateTextNode(src)); sources.AppendChild(source); } XmlElement packages = xml.CreateElement("packages"); foreach (var module in result.Modules) { XmlElement package = xml.CreateElement("package"); package.SetAttribute("line-rate", summary.CalculateLineCoverage(module.Value).ToString()); package.SetAttribute("branch-rate", summary.CalculateBranchCoverage(module.Value).ToString()); package.SetAttribute("complexity", "0"); XmlElement classes = xml.CreateElement("classes"); foreach (var document in module.Value) { foreach (var cls in document.Value) { XmlElement @class = xml.CreateElement("class"); @class.SetAttribute("name", cls.Key); @class.SetAttribute("filename", Path.GetFileName(document.Key)); @class.SetAttribute("line-rate", summary.CalculateLineCoverage(cls.Value).ToString()); @class.SetAttribute("branch-rate", summary.CalculateBranchCoverage(cls.Value).ToString()); @class.SetAttribute("complexity", "0"); XmlElement methods = xml.CreateElement("methods"); foreach (var meth in cls.Value) { XmlElement method = xml.CreateElement("method"); method.SetAttribute("name", meth.Key.Split(':')[2].Split('(')[0]); method.SetAttribute("signature", meth.Key); method.SetAttribute("line-rate", summary.CalculateLineCoverage(meth.Value).ToString()); method.SetAttribute("branch-rate", summary.CalculateBranchCoverage(meth.Value).ToString()); XmlElement lines = xml.CreateElement("lines"); foreach (var ln in meth.Value) { XmlElement line = xml.CreateElement("line"); line.SetAttribute("number", ln.Key.ToString()); line.SetAttribute("hits", ln.Value.Hits.ToString()); line.SetAttribute("branch", ln.Value.IsBranchPoint.ToString()); lines.AppendChild(line); } method.AppendChild(lines); methods.AppendChild(method); } @class.AppendChild(methods); classes.AppendChild(@class); } } package.AppendChild(classes); packages.AppendChild(package); } coverage.AppendChild(sources); coverage.AppendChild(packages); xml.AppendChild(coverage); StringWriter writer = new StringWriter(); xml.Save(writer); return(writer.ToString()); }
public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { CoverageSummary summary = new CoverageSummary(); var lineCoverage = summary.CalculateLineCoverage(result.Modules); var branchCoverage = summary.CalculateBranchCoverage(result.Modules); XDocument xml = new XDocument(); XElement coverage = new XElement("coverage"); coverage.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(result.Modules).Percent / 100).ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("version", "1.9")); coverage.Add(new XAttribute("timestamp", (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds)); XElement sources = new XElement("sources"); List <string> absolutePaths = new List <string>(); if (!result.Parameters.DeterministicReport) { absolutePaths = GetBasePaths(result.Modules, result.Parameters.UseSourceLink).ToList(); absolutePaths.ForEach(x => sources.Add(new XElement("source", x))); } XElement packages = new XElement("packages"); foreach (var module in result.Modules) { XElement package = new XElement("package"); package.Add(new XAttribute("name", Path.GetFileNameWithoutExtension(module.Key))); package.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); package.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(module.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); package.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(module.Value))); XElement classes = new XElement("classes"); foreach (var document in module.Value) { foreach (var cls in document.Value) { XElement @class = new XElement("class"); @class.Add(new XAttribute("name", cls.Key)); string fileName; if (!result.Parameters.DeterministicReport) { fileName = GetRelativePathFromBase(absolutePaths, document.Key, result.Parameters.UseSourceLink); } else { fileName = sourceRootTranslator.ResolveDeterministicPath(document.Key); } @class.Add(new XAttribute("filename", fileName)); @class.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(cls.Value).Percent / 100).ToString(CultureInfo.InvariantCulture))); @class.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(cls.Value))); XElement classLines = new XElement("lines"); XElement methods = new XElement("methods"); foreach (var meth in cls.Value) { // Skip all methods with no lines if (meth.Value.Lines.Count == 0) { continue; } XElement method = new XElement("method"); method.Add(new XAttribute("name", meth.Key.Split(':').Last().Split('(').First())); method.Add(new XAttribute("signature", "(" + meth.Key.Split(':').Last().Split('(').Last())); method.Add(new XAttribute("line-rate", (summary.CalculateLineCoverage(meth.Value.Lines).Percent / 100).ToString(CultureInfo.InvariantCulture))); method.Add(new XAttribute("branch-rate", (summary.CalculateBranchCoverage(meth.Value.Branches).Percent / 100).ToString(CultureInfo.InvariantCulture))); method.Add(new XAttribute("complexity", summary.CalculateCyclomaticComplexity(meth.Value.Branches))); XElement lines = new XElement("lines"); foreach (var ln in meth.Value.Lines) { bool isBranchPoint = meth.Value.Branches.Any(b => b.Line == ln.Key); XElement line = new XElement("line"); line.Add(new XAttribute("number", ln.Key.ToString())); line.Add(new XAttribute("hits", ln.Value.ToString())); line.Add(new XAttribute("branch", isBranchPoint.ToString())); if (isBranchPoint) { var branches = meth.Value.Branches.Where(b => b.Line == ln.Key).ToList(); var branchInfoCoverage = summary.CalculateBranchCoverage(branches); line.Add(new XAttribute("condition-coverage", $"{branchInfoCoverage.Percent.ToString(CultureInfo.InvariantCulture)}% ({branchInfoCoverage.Covered.ToString(CultureInfo.InvariantCulture)}/{branchInfoCoverage.Total.ToString(CultureInfo.InvariantCulture)})")); XElement conditions = new XElement("conditions"); var byOffset = branches.GroupBy(b => b.Offset).ToDictionary(b => b.Key, b => b.ToList()); foreach (var entry in byOffset) { XElement condition = new XElement("condition"); 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("coverage", $"{summary.CalculateBranchCoverage(entry.Value).Percent.ToString(CultureInfo.InvariantCulture)}%")); conditions.Add(condition); } line.Add(conditions); } lines.Add(line); classLines.Add(line); } method.Add(lines); methods.Add(method); } @class.Add(methods); @class.Add(classLines); classes.Add(@class); } } package.Add(classes); packages.Add(package); } coverage.Add(new XAttribute("lines-covered", lineCoverage.Covered.ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("lines-valid", lineCoverage.Total.ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("branches-covered", branchCoverage.Covered.ToString(CultureInfo.InvariantCulture))); coverage.Add(new XAttribute("branches-valid", branchCoverage.Total.ToString(CultureInfo.InvariantCulture))); coverage.Add(sources); coverage.Add(packages); xml.Add(coverage); var stream = new MemoryStream(); xml.Save(stream); return(Encoding.UTF8.GetString(stream.ToArray())); }
public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { if (result.Parameters.DeterministicReport) { throw new NotSupportedException("Deterministic report not supported by openCover reporter"); } var summary = new CoverageSummary(); var xml = new XDocument(); var coverage = new XElement("CoverageSession"); var coverageSummary = new XElement("Summary"); var modules = new XElement("Modules"); int numClasses = 0, numMethods = 0; int visitedClasses = 0, visitedMethods = 0; int i = 1; foreach (System.Collections.Generic.KeyValuePair <string, Documents> mod in result.Modules) { var module = new XElement("Module"); module.Add(new XAttribute("hash", Guid.NewGuid().ToString().ToUpper())); var path = new XElement("ModulePath", mod.Key); var time = new XElement("ModuleTime", DateTime.UtcNow.ToString("yyyy-MM-ddThh:mm:ss")); var name = new XElement("ModuleName", Path.GetFileNameWithoutExtension(mod.Key)); module.Add(path); module.Add(time); module.Add(name); var files = new XElement("Files"); var classes = new XElement("Classes"); foreach (System.Collections.Generic.KeyValuePair <string, Classes> doc in mod.Value) { var file = new XElement("File"); file.Add(new XAttribute("uid", i.ToString())); file.Add(new XAttribute("fullPath", doc.Key)); files.Add(file); foreach (System.Collections.Generic.KeyValuePair <string, Methods> cls in doc.Value) { var @class = new XElement("Class"); var classSummary = new XElement("Summary"); var className = new XElement("FullName", cls.Key); var methods = new XElement("Methods"); int j = 0; bool classVisited = false; foreach (System.Collections.Generic.KeyValuePair <string, Method> meth in cls.Value) { // Skip all methods with no lines if (meth.Value.Lines.Count == 0) { continue; } CoverageDetails methLineCoverage = summary.CalculateLineCoverage(meth.Value.Lines); CoverageDetails methBranchCoverage = summary.CalculateBranchCoverage(meth.Value.Branches); int methCyclomaticComplexity = summary.CalculateCyclomaticComplexity(meth.Value.Branches); int methNpathComplexity = summary.CalculateNpathComplexity(meth.Value.Branches); var method = new XElement("Method"); method.Add(new XAttribute("cyclomaticComplexity", methCyclomaticComplexity.ToString())); method.Add(new XAttribute("nPathComplexity", methCyclomaticComplexity.ToString())); method.Add(new XAttribute("sequenceCoverage", methLineCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); method.Add(new XAttribute("branchCoverage", methBranchCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); method.Add(new XAttribute("isConstructor", meth.Key.Contains("ctor").ToString())); method.Add(new XAttribute("isGetter", meth.Key.Contains("get_").ToString())); method.Add(new XAttribute("isSetter", meth.Key.Contains("set_").ToString())); method.Add(new XAttribute("isStatic", (!meth.Key.Contains("get_") || !meth.Key.Contains("set_")).ToString())); var methodName = new XElement("Name", meth.Key); var fileRef = new XElement("FileRef"); fileRef.Add(new XAttribute("uid", i.ToString())); var methodPoint = new XElement("MethodPoint"); methodPoint.Add(new XAttribute("vc", methLineCoverage.Covered.ToString())); methodPoint.Add(new XAttribute("uspid", "0")); methodPoint.Add(new XAttribute(XName.Get("type", "xsi"), "SequencePoint")); methodPoint.Add(new XAttribute("ordinal", j.ToString())); methodPoint.Add(new XAttribute("offset", j.ToString())); methodPoint.Add(new XAttribute("sc", "0")); methodPoint.Add(new XAttribute("sl", meth.Value.Lines.First().Key.ToString())); methodPoint.Add(new XAttribute("ec", "1")); methodPoint.Add(new XAttribute("el", meth.Value.Lines.Last().Key.ToString())); methodPoint.Add(new XAttribute("bec", "0")); methodPoint.Add(new XAttribute("bev", "0")); methodPoint.Add(new XAttribute("fileid", i.ToString())); // They're really just lines var sequencePoints = new XElement("SequencePoints"); var branchPoints = new XElement("BranchPoints"); var methodSummary = new XElement("Summary"); int k = 0; int kBr = 0; bool methodVisited = false; foreach (System.Collections.Generic.KeyValuePair <int, int> lines in meth.Value.Lines) { BranchInfo[] lineBranches = meth.Value.Branches.Where(branchInfo => branchInfo.Line == lines.Key).ToArray(); CoverageDetails branchCoverage = summary.CalculateBranchCoverage(lineBranches); var sequencePoint = new XElement("SequencePoint"); sequencePoint.Add(new XAttribute("vc", lines.Value.ToString())); sequencePoint.Add(new XAttribute("uspid", lines.Key.ToString())); sequencePoint.Add(new XAttribute("ordinal", k.ToString())); sequencePoint.Add(new XAttribute("sl", lines.Key.ToString())); sequencePoint.Add(new XAttribute("sc", "1")); sequencePoint.Add(new XAttribute("el", lines.Key.ToString())); sequencePoint.Add(new XAttribute("ec", "2")); sequencePoint.Add(new XAttribute("bec", branchCoverage.Total)); sequencePoint.Add(new XAttribute("bev", branchCoverage.Covered)); sequencePoint.Add(new XAttribute("fileid", i.ToString())); sequencePoints.Add(sequencePoint); if (lines.Value > 0) { classVisited = true; methodVisited = true; } k++; } foreach (BranchInfo branche in meth.Value.Branches) { var branchPoint = new XElement("BranchPoint"); branchPoint.Add(new XAttribute("vc", branche.Hits.ToString())); branchPoint.Add(new XAttribute("uspid", branche.Line.ToString())); branchPoint.Add(new XAttribute("ordinal", branche.Ordinal.ToString())); branchPoint.Add(new XAttribute("path", branche.Path.ToString())); branchPoint.Add(new XAttribute("offset", branche.Offset.ToString())); branchPoint.Add(new XAttribute("offsetend", branche.EndOffset.ToString())); branchPoint.Add(new XAttribute("sl", branche.Line.ToString())); branchPoint.Add(new XAttribute("fileid", i.ToString())); branchPoints.Add(branchPoint); kBr++; } numMethods++; if (methodVisited) { visitedMethods++; } methodSummary.Add(new XAttribute("numSequencePoints", methLineCoverage.Total.ToString())); methodSummary.Add(new XAttribute("visitedSequencePoints", methLineCoverage.Covered.ToString())); methodSummary.Add(new XAttribute("numBranchPoints", methBranchCoverage.Total.ToString())); methodSummary.Add(new XAttribute("visitedBranchPoints", methBranchCoverage.Covered.ToString())); methodSummary.Add(new XAttribute("sequenceCoverage", methLineCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); methodSummary.Add(new XAttribute("branchCoverage", methBranchCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); methodSummary.Add(new XAttribute("maxCyclomaticComplexity", methCyclomaticComplexity.ToString())); methodSummary.Add(new XAttribute("minCyclomaticComplexity", methCyclomaticComplexity.ToString())); methodSummary.Add(new XAttribute("visitedClasses", "0")); methodSummary.Add(new XAttribute("numClasses", "0")); methodSummary.Add(new XAttribute("visitedMethods", methodVisited ? "1" : "0")); methodSummary.Add(new XAttribute("numMethods", "1")); method.Add(methodSummary); method.Add(new XElement("MetadataToken")); method.Add(methodName); method.Add(fileRef); method.Add(sequencePoints); method.Add(branchPoints); method.Add(methodPoint); methods.Add(method); j++; } numClasses++; if (classVisited) { visitedClasses++; } CoverageDetails classLineCoverage = summary.CalculateLineCoverage(cls.Value); CoverageDetails classBranchCoverage = summary.CalculateBranchCoverage(cls.Value); CoverageDetails classMethodCoverage = summary.CalculateMethodCoverage(cls.Value); int classMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(cls.Value); int classMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(cls.Value); classSummary.Add(new XAttribute("numSequencePoints", classLineCoverage.Total.ToString())); classSummary.Add(new XAttribute("visitedSequencePoints", classLineCoverage.Covered.ToString())); classSummary.Add(new XAttribute("numBranchPoints", classBranchCoverage.Total.ToString())); classSummary.Add(new XAttribute("visitedBranchPoints", classBranchCoverage.Covered.ToString())); classSummary.Add(new XAttribute("sequenceCoverage", classLineCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); classSummary.Add(new XAttribute("branchCoverage", classBranchCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); classSummary.Add(new XAttribute("maxCyclomaticComplexity", classMaxCyclomaticComplexity.ToString())); classSummary.Add(new XAttribute("minCyclomaticComplexity", classMinCyclomaticComplexity.ToString())); classSummary.Add(new XAttribute("visitedClasses", classVisited ? "1" : "0")); classSummary.Add(new XAttribute("numClasses", "1")); classSummary.Add(new XAttribute("visitedMethods", classMethodCoverage.Covered.ToString())); classSummary.Add(new XAttribute("numMethods", classMethodCoverage.Total.ToString())); @class.Add(classSummary); @class.Add(className); @class.Add(methods); classes.Add(@class); } i++; } module.Add(files); module.Add(classes); modules.Add(module); } CoverageDetails moduleLineCoverage = summary.CalculateLineCoverage(result.Modules); CoverageDetails moduleBranchCoverage = summary.CalculateBranchCoverage(result.Modules); int moduleMaxCyclomaticComplexity = summary.CalculateMaxCyclomaticComplexity(result.Modules); int moduleMinCyclomaticComplexity = summary.CalculateMinCyclomaticComplexity(result.Modules); coverageSummary.Add(new XAttribute("numSequencePoints", moduleLineCoverage.Total.ToString())); coverageSummary.Add(new XAttribute("visitedSequencePoints", moduleLineCoverage.Covered.ToString())); coverageSummary.Add(new XAttribute("numBranchPoints", moduleBranchCoverage.Total.ToString())); coverageSummary.Add(new XAttribute("visitedBranchPoints", moduleBranchCoverage.Covered.ToString())); coverageSummary.Add(new XAttribute("sequenceCoverage", moduleLineCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); coverageSummary.Add(new XAttribute("branchCoverage", moduleBranchCoverage.Percent.ToString("G", CultureInfo.InvariantCulture))); coverageSummary.Add(new XAttribute("maxCyclomaticComplexity", moduleMaxCyclomaticComplexity.ToString())); coverageSummary.Add(new XAttribute("minCyclomaticComplexity", moduleMinCyclomaticComplexity.ToString())); coverageSummary.Add(new XAttribute("visitedClasses", visitedClasses.ToString())); coverageSummary.Add(new XAttribute("numClasses", numClasses.ToString())); coverageSummary.Add(new XAttribute("visitedMethods", visitedMethods.ToString())); coverageSummary.Add(new XAttribute("numMethods", numMethods.ToString())); coverage.Add(coverageSummary); coverage.Add(modules); xml.Add(coverage); var stream = new MemoryStream(); xml.Save(stream); return(Encoding.UTF8.GetString(stream.ToArray())); }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); var coverage = InstrumentationTask.Coverage; var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_output); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } var formats = _format.Split(','); foreach (var format in formats) { 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 Console.WriteLine(" Outputting results to console"); Console.WriteLine(reporter.Report(result)); } else { // Output to file var filename = Path.GetFileName(_output); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); Console.WriteLine($" Generating report '{report}'"); var resultContent = reporter.Report(result); // Possible concurrency access to merge file between write/read on parallel testing RetryHelper.Retry(() => { using (var file = File.Open(report, FileMode.Create, FileAccess.Write, FileShare.None)) { using (var writer = new StreamWriter(file)) { writer.Write(resultContent); } } }, () => TimeSpan.FromMilliseconds(100), 5); } } var thresholdTypeFlags = ThresholdTypeFlags.None; var thresholdStat = ThresholdStatistic.Minimum; foreach (var thresholdType in _thresholdType.Split(',').Select(t => t.Trim())) { 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; } } if (_thresholdStat.Equals("average", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Average; } else if (_thresholdStat.Equals("total", StringComparison.OrdinalIgnoreCase)) { thresholdStat = ThresholdStatistic.Total; } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); int numModules = result.Modules.Count; var totalLinePercent = summary.CalculateLineCoverage(result.Modules).Percent * 100; var totalBranchPercent = summary.CalculateBranchCoverage(result.Modules).Percent * 100; var totalMethodPercent = summary.CalculateMethodCoverage(result.Modules).Percent * 100; 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}%"); } Console.WriteLine(); Console.WriteLine(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}%"); Console.WriteLine(coverageTable.ToStringAlternative()); thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, _threshold, thresholdTypeFlags, thresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} line coverage is below the specified {_threshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} branch coverage is below the specified {_threshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {thresholdStat.ToString().ToLower()} method coverage is below the specified {_threshold}"); } throw new Exception(exceptionMessageBuilder.ToString()); } } catch (Exception ex) { _logger.LogError(ex); return(false); } return(true); }