public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, Dictionary <ThresholdTypeFlags, double> thresholdTypeFlagValues, ThresholdStatistic thresholdStat) { ThresholdTypeFlags thresholdTypeFlags = ThresholdTypeFlags.None; switch (thresholdStat) { case ThresholdStatistic.Minimum: { if (!Modules.Any()) { thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, 0, 0, 0); } foreach (KeyValuePair <string, Documents> module in Modules) { double line = summary.CalculateLineCoverage(module.Value).Percent; double branch = summary.CalculateBranchCoverage(module.Value).Percent; double method = summary.CalculateMethodCoverage(module.Value).Percent; thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, line, branch, method); } } break; case ThresholdStatistic.Average: { double line = summary.CalculateLineCoverage(Modules).AverageModulePercent; double branch = summary.CalculateBranchCoverage(Modules).AverageModulePercent; double method = summary.CalculateMethodCoverage(Modules).AverageModulePercent; thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, line, branch, method); } break; case ThresholdStatistic.Total: { double line = summary.CalculateLineCoverage(Modules).Percent; double branch = summary.CalculateBranchCoverage(Modules).Percent; double method = summary.CalculateMethodCoverage(Modules).Percent; thresholdTypeFlags = CompareThresholdValues(thresholdTypeFlagValues, thresholdTypeFlags, line, branch, method); } break; } return(thresholdTypeFlags); }
public void TestGetThresholdTypesBelowThresholdMethod() { CoverageResult result = new CoverageResult(); result.Modules = _modules; CoverageSummary summary = new CoverageSummary(); Dictionary <ThresholdTypeFlags, double> thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>() { { ThresholdTypeFlags.Line, 50 }, { ThresholdTypeFlags.Method, 75 }, { ThresholdTypeFlags.Branch, 10 }, }; ThresholdStatistic thresholdStatic = ThresholdStatistic.Minimum; ThresholdTypeFlags resThresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStatic); Assert.Equal(ThresholdTypeFlags.Method, resThresholdTypeFlags); }
public void TestGetThresholdTypesBelowThresholdAllGood() { var result = new CoverageResult(); result.Modules = _modules; var summary = new CoverageSummary(); var thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>() { { ThresholdTypeFlags.Line, 50 }, { ThresholdTypeFlags.Method, 50 }, { ThresholdTypeFlags.Branch, 50 }, }; ThresholdStatistic thresholdStatic = ThresholdStatistic.Average; ThresholdTypeFlags resThresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStatic); Assert.Equal(ThresholdTypeFlags.None, resThresholdTypeFlags); }
public void TestGetThresholdTypesBelowThresholdWhenNoModuleInstrumented() { CoverageResult result = new CoverageResult(); result.Modules = new Modules(); CoverageSummary summary = new CoverageSummary(); Dictionary <ThresholdTypeFlags, double> thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>() { { ThresholdTypeFlags.Line, 80 }, { ThresholdTypeFlags.Method, 80 }, { ThresholdTypeFlags.Branch, 80 }, }; ThresholdTypeFlags thresholdTypeFlags = ThresholdTypeFlags.Line | ThresholdTypeFlags.Branch | ThresholdTypeFlags.Method; ThresholdStatistic thresholdStatic = ThresholdStatistic.Minimum; ThresholdTypeFlags resThresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStatic); Assert.Equal(thresholdTypeFlags, resThresholdTypeFlags); }
public void TestGetThresholdTypesBelowThresholdAllFail() { var result = new CoverageResult(); result.Modules = _modules; var summary = new CoverageSummary(); var thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>() { { ThresholdTypeFlags.Line, 100 }, { ThresholdTypeFlags.Method, 100 }, { ThresholdTypeFlags.Branch, 100 }, }; ThresholdTypeFlags thresholdTypeFlags = ThresholdTypeFlags.Line | ThresholdTypeFlags.Branch | ThresholdTypeFlags.Method; ThresholdStatistic thresholdStatic = ThresholdStatistic.Minimum; ThresholdTypeFlags resThresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, thresholdTypeFlagValues, thresholdStatic); Assert.Equal(thresholdTypeFlags, resThresholdTypeFlags); }
private static ThresholdTypeFlags CompareThresholdValues( Dictionary <ThresholdTypeFlags, double> thresholdTypeFlagValues, ThresholdTypeFlags thresholdTypeFlags, double line, double branch, double method) { if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Line, out var lineThresholdValue) && lineThresholdValue > line) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Branch, out var branchThresholdValue) && branchThresholdValue > branch) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } if (thresholdTypeFlagValues.TryGetValue(ThresholdTypeFlags.Method, out var methodThresholdValue) && methodThresholdValue > method) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } return(thresholdTypeFlags); }
public ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, double threshold, ThresholdTypeFlags thresholdTypes, ThresholdStatistic thresholdStat) { var thresholdTypeFlags = ThresholdTypeFlags.None; switch (thresholdStat) { case ThresholdStatistic.Minimum: { foreach (var module in Modules) { var line = summary.CalculateLineCoverage(module.Value).Percent * 100; var branch = summary.CalculateBranchCoverage(module.Value).Percent * 100; var method = summary.CalculateMethodCoverage(module.Value).Percent * 100; if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { if (line < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } } if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { if (branch < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } } if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { if (method < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } } } break; case ThresholdStatistic.Average: { double line = 0; double branch = 0; double method = 0; int numModules = Modules.Count; foreach (var module in Modules) { line += summary.CalculateLineCoverage(module.Value).Percent * 100; branch += summary.CalculateBranchCoverage(module.Value).Percent * 100; method += summary.CalculateMethodCoverage(module.Value).Percent * 100; } if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { if ((line / numModules) < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } } if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { if ((branch / numModules) < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } } if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { if ((method / numModules) < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } } break; case ThresholdStatistic.Total: { var line = summary.CalculateLineCoverage(Modules).Percent * 100; var branch = summary.CalculateBranchCoverage(Modules).Percent * 100; var method = summary.CalculateMethodCoverage(Modules).Percent * 100; if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { if (line < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } } if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { if (branch < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } } if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { if (method < threshold) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } } break; } return(thresholdTypeFlags); }
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); }
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>(); IFileSystem 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); CommandOption instrumentModulesWithoutLocalSources = app.Option("--instrument-modules-without-local-sources", "Specifies whether modules should be instrumented even if the sources from the PDBs can't be found locally.", CommandOptionType.NoValue); 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(), InstrumentModulesWithoutLocalSources = instrumentModulesWithoutLocalSources.HasValue(), }; 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(); string dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); List <string> dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List <string>(new string[] { "line", "branch", "method" }); ThresholdStatistic dThresholdStat = thresholdStat.HasValue() ? Enum.Parse <ThresholdStatistic>(thresholdStat.Value(), true) : Enum.Parse <ThresholdStatistic>("minimum", true); logger.LogInformation("\nCalculating coverage result..."); CoverageResult result = coverage.GetCoverageResult(); string directory = Path.GetDirectoryName(dOutput); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } foreach (string format in formats.HasValue() ? formats.Values : new List <string>(new string[] { "json" })) { Core.Abstractions.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 logger.LogInformation(" Outputting results to console", important: true); logger.LogInformation(reporter.Report(result, sourceRootTranslator), important: true); } else { // Output to file string filename = Path.GetFileName(dOutput); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; string 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 (string 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); } } var thresholdTypeFlagValues = new Dictionary <ThresholdTypeFlags, double>(); if (threshold.HasValue() && threshold.Value().Contains(',')) { IEnumerable <string> 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 (string thresholdValue in thresholdValues) { if (double.TryParse(thresholdValue, out double 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(); 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)}%"); } logger.LogInformation(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)}%"); logger.LogInformation(coverageTable.ToStringAlternative()); if (process.ExitCode > 0) { exitCode += (int)CommandExitCodes.TestFailed; } ThresholdTypeFlags 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 ThresholdTypeFlags GetThresholdTypesBelowThreshold(CoverageSummary summary, Dictionary <ThresholdTypeFlags, double> thresholdTypeFlagValues, ThresholdTypeFlags thresholdTypes, ThresholdStatistic thresholdStat) { var thresholdTypeFlags = ThresholdTypeFlags.None; switch (thresholdStat) { case ThresholdStatistic.Minimum: { foreach (var module in Modules) { double line = summary.CalculateLineCoverage(module.Value).Percent; double branch = summary.CalculateBranchCoverage(module.Value).Percent; double method = summary.CalculateMethodCoverage(module.Value).Percent; if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { if (line < thresholdTypeFlagValues[ThresholdTypeFlags.Line]) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } } if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { if (branch < thresholdTypeFlagValues[ThresholdTypeFlags.Branch]) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } } if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { if (method < thresholdTypeFlagValues[ThresholdTypeFlags.Method]) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } } } break; case ThresholdStatistic.Average: { double line = summary.CalculateLineCoverage(Modules).AverageModulePercent; double branch = summary.CalculateBranchCoverage(Modules).AverageModulePercent; double method = summary.CalculateMethodCoverage(Modules).AverageModulePercent; if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { if ((line / numModules) < thresholdTypeFlagValues[ThresholdTypeFlags.Line]) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } } if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { if ((branch / numModules) < thresholdTypeFlagValues[ThresholdTypeFlags.Branch]) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } } if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { if ((method / numModules) < thresholdTypeFlagValues[ThresholdTypeFlags.Method]) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } } break; case ThresholdStatistic.Total: { double line = summary.CalculateLineCoverage(Modules).Percent; double branch = summary.CalculateBranchCoverage(Modules).Percent; double method = summary.CalculateMethodCoverage(Modules).Percent; if ((thresholdTypes & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { if (line < thresholdTypeFlagValues[ThresholdTypeFlags.Line]) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } } if ((thresholdTypes & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { if (branch < thresholdTypeFlagValues[ThresholdTypeFlags.Branch]) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } } if ((thresholdTypes & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { if (method < thresholdTypeFlagValues[ThresholdTypeFlags.Method]) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } } break; } return(thresholdTypeFlags); }