Beispiel #1
0
        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);
        }
Beispiel #3
0
        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);
        }
Beispiel #5
0
        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);
        }
Beispiel #6
0
        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);
        }
Beispiel #7
0
        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);
        }
Beispiel #8
0
        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);
        }
Beispiel #9
0
        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);
        }