public void WillPublishCoverageSummaryToTfs() { var summary = new CoverageSummary("flavor", "platform"); summary.AddCoverageStatistics("lines", 10, 5, CoverageSummary.Priority.Line); summary.AddCoverageStatistics("blocks", 10, 5, CoverageSummary.Priority.Other); var token = new CancellationToken(); var publisher = new AzurePipelinesPublisher(_context, _mockClientFactory.Object, _mockFFHelper.Object, _mockHtmlPublisher.Object, _mockLogStoreHelper.Object, true); var mockClient = new Mock <TestManagementHttpClient>(new Uri("http://localhost"), new VssCredentials()); _mockClientFactory.Setup(x => x.GetClient <TestManagementHttpClient>()).Returns(mockClient.Object); _mockFFHelper.Setup(x => x.GetFeatureFlagState( It.Is <string>(a => a == Constants.FeatureFlags.EnablePublishToTcmServiceDirectlyFromTaskFF), It.Is <bool>(b => b == false))).Returns(false); publisher.PublishCoverageSummary(summary, token).Wait(); mockClient.Verify(x => x.UpdateCodeCoverageSummaryAsync( It.Is <CodeCoverageData>(a => a == summary.CodeCoverageData), It.Is <Guid>(b => b == _context.ProjectId), It.Is <int>(c => c == _context.BuildId), It.IsAny <object>(), It.Is <CancellationToken>(d => d == token))); }
public CoverageSummary GetCoverageSummary() { TraceLogger.Debug("ReportGeneratorTool.GetCoverageSummary: Generating coverage summary for the coverage files."); var summary = new CoverageSummary(); if (Configuration.CoverageFiles == null) { return(summary); } int totalLines = 0; int coveredLines = 0; foreach (var assembly in _parserResult.Assemblies) { foreach (var @class in assembly.Classes) { foreach (var file in @class.Files) { totalLines += file.CoverableLines; coveredLines += file.CoveredLines; } } } summary.AddCoverageStatistics("line", totalLines, coveredLines, CoverageSummary.Priority.Line); return(summary); }
public void TestCalculateSummary() { CoverageResult result = new CoverageResult(); result.Identifier = Guid.NewGuid().ToString(); Lines lines = new Lines(); lines.Add(1, 1); lines.Add(2, 0); Methods methods = new Methods(); methods.Add("System.Void Coverlet.Core.Tests.CoverageSummaryTests.TestCalculateSummary()", lines); Classes classes = new Classes(); classes.Add("Coverlet.Core.Tests.CoverageSummaryTests", methods); Documents documents = new Documents(); documents.Add("doc.cs", classes); result.Modules = new Modules(); result.Modules.Add("module", documents); CoverageSummary summary = new CoverageSummary(result); CoverageSummaryResult summaryResult = summary.CalculateSummary(); Assert.NotEmpty(summaryResult); Assert.True(summaryResult.ContainsKey("module")); Assert.Equal(50, summaryResult["module"]); }
public string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { if (result.Parameters.DeterministicReport) { throw new NotSupportedException("Deterministic report not supported by lcov reporter"); } CoverageSummary summary = new CoverageSummary(); List <string> lcov = new List <string>(); foreach (var module in result.Modules) { foreach (var doc in module.Value) { var docLineCoverage = summary.CalculateLineCoverage(doc.Value); var docBranchCoverage = summary.CalculateBranchCoverage(doc.Value); var docMethodCoverage = summary.CalculateMethodCoverage(doc.Value); lcov.Add("SF:" + doc.Key); foreach (var @class in doc.Value) { foreach (var method in @class.Value) { // Skip all methods with no lines if (method.Value.Lines.Count == 0) { continue; } lcov.Add($"FN:{method.Value.Lines.First().Key - 1},{method.Key}"); lcov.Add($"FNDA:{method.Value.Lines.First().Value},{method.Key}"); foreach (var line in method.Value.Lines) { lcov.Add($"DA:{line.Key},{line.Value}"); } foreach (var branch in method.Value.Branches) { lcov.Add($"BRDA:{branch.Line},{branch.Offset},{branch.Path},{branch.Hits}"); } } } lcov.Add($"LF:{docLineCoverage.Total}"); lcov.Add($"LH:{docLineCoverage.Covered}"); lcov.Add($"BRF:{docBranchCoverage.Total}"); lcov.Add($"BRH:{docBranchCoverage.Covered}"); lcov.Add($"FNF:{docMethodCoverage.Total}"); lcov.Add($"FNH:{docMethodCoverage.Covered}"); lcov.Add("end_of_record"); } } return(string.Join(Environment.NewLine, lcov)); }
public string Report(CoverageResult result) { CoverageSummary summary = new CoverageSummary(); List <string> lcov = new List <string>(); foreach (var module in result.Modules) { foreach (var doc in module.Value) { var docLineCoverage = summary.CalculateLineCoverage(doc.Value); var docBranchCoverage = summary.CalculateBranchCoverage(doc.Value); var docMethodCoverage = summary.CalculateMethodCoverage(doc.Value); lcov.Add("SF:" + doc.Key); foreach (var @class in doc.Value) { foreach (var method in @class.Value) { // Skip all methods with no lines if (method.Value.Lines.Count == 0) { continue; } lcov.Add($"FN:{method.Value.Lines.First().Key - 1},{method.Key}"); lcov.Add($"FNDA:{method.Value.Lines.First().Value.Hits},{method.Key}"); foreach (var line in method.Value.Lines) { lcov.Add($"DA:{line.Key},{line.Value.Hits}"); } foreach (var branchs in method.Value.Branches) { foreach (var branch in branchs.Value) { lcov.Add($"BRDA:{branchs.Key},{branch.Offset},{branch.Path},{branch.Hits}"); } } } } lcov.Add($"LF:{docLineCoverage.Total}"); lcov.Add($"LH:{docLineCoverage.Covered}"); lcov.Add($"BRF:{docBranchCoverage.Total}"); lcov.Add($"BRH:{docBranchCoverage.Covered}"); lcov.Add($"FNF:{docMethodCoverage.Total}"); lcov.Add($"FNH:{docMethodCoverage.Covered}"); lcov.Add("end_of_record"); } } return(string.Join(Environment.NewLine, lcov)); }
public void TestCalculateBranchCoverage_MultiModule() { var summary = new CoverageSummary(); Documents documentsFirstModule = _averageCalculationMultiModule["module"]; Documents documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; Assert.Equal(83.33, summary.CalculateBranchCoverage(_averageCalculationMultiModule).AverageModulePercent); Assert.Equal(100, summary.CalculateBranchCoverage(documentsFirstModule.First().Value).Percent); Assert.Equal(66.66, summary.CalculateBranchCoverage(documentsSecondModule.First().Value).Percent); }
public void TestCalculateMethodCoverage_MultiModule() { CoverageSummary summary = new CoverageSummary(); var documentsFirstModule = _averageCalculationMultiModule["module"]; var documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; Assert.Equal(75, summary.CalculateMethodCoverage(_averageCalculationMultiModule).AverageModulePercent); Assert.Equal(100, summary.CalculateMethodCoverage(documentsFirstModule.First().Value).Percent); Assert.Equal(50, summary.CalculateMethodCoverage(documentsSecondModule.First().Value).Percent); }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); var coverage = InstrumentationTask.Coverage; CoverageResult result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_filename); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } Console.WriteLine($" Generating report '{_filename}'"); IReporter reporter = default(IReporter); switch (_format) { case "lcov": reporter = new LcovReporter(); break; case "opencover": reporter = new OpenCoverReporter(); break; default: reporter = new JsonReporter(); break; } File.WriteAllText(_filename, result.Format(reporter)); CoverageSummary coverageSummary = new CoverageSummary(result); var summary = coverageSummary.CalculateSummary(); ConsoleTable table = new ConsoleTable("Module", "Coverage"); foreach (var item in summary) { table.AddRow(item.Key, $"{item.Value}%"); } Console.WriteLine(); table.Write(Format.Alternative); } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public void TestCalculateLineCoverage_NoModules() { CoverageSummary summary = new CoverageSummary(); var modules = new Modules(); Assert.Equal(0, summary.CalculateLineCoverage(modules).Percent); Assert.Equal(0, summary.CalculateLineCoverage(modules).AverageModulePercent); Assert.Equal(0, summary.CalculateBranchCoverage(modules).Percent); Assert.Equal(0, summary.CalculateBranchCoverage(modules).AverageModulePercent); Assert.Equal(0, summary.CalculateMethodCoverage(modules).Percent); Assert.Equal(0, summary.CalculateMethodCoverage(modules).AverageModulePercent); }
public override bool Execute() { try { Console.WriteLine("\nCalculating coverage result..."); var coverage = InstrumentationTask.Coverage; CoverageResult result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(_filename); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } IReporter reporter = new ReporterFactory(_format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{_format}' is not supported"); } _filename = _filename + "." + reporter.Extension; Console.WriteLine($" Generating report '{_filename}'"); File.WriteAllText(_filename, reporter.Report(result)); double total = 0; CoverageSummary summary = new CoverageSummary(); ConsoleTable table = new ConsoleTable("Module", "Coverage"); foreach (var module in result.Modules) { double percent = summary.CalculateLineCoverage(module.Value) * 100; table.AddRow(System.IO.Path.GetFileNameWithoutExtension(module.Key), $"{percent}%"); total += percent; } Console.WriteLine(); Console.WriteLine(table.ToMarkDownString()); double average = total / result.Modules.Count; if (average < _threshold) { return(false); } } catch (Exception ex) { Log.LogErrorFromException(ex); return(false); } return(true); }
public void TestCalculateLineCoverage_MultiModule() { CoverageSummary summary = new CoverageSummary(); var documentsFirstModule = _averageCalculationMultiModule["module"]; var documentsSecondModule = _averageCalculationMultiModule["aditionalModule"]; Assert.Equal(37.5, summary.CalculateLineCoverage(_averageCalculationMultiModule).AverageModulePercent); Assert.Equal(50, summary.CalculateLineCoverage(documentsFirstModule.First().Value).Percent); Assert.Equal(33.33, summary.CalculateLineCoverage(documentsSecondModule.First().Value.First().Value.ElementAt(0).Value.Lines).Percent); // covered 1 of 3 Assert.Equal(0, summary.CalculateLineCoverage(documentsSecondModule.First().Value.First().Value.ElementAt(1).Value.Lines).Percent); // covered 0 of 1 Assert.Equal(25, summary.CalculateLineCoverage(documentsSecondModule.First().Value).Percent); // covered 1 of 4 lines }
public void TestCalculateMethodCoverage() { CoverageSummary summary = new CoverageSummary(); var module = _modules.First(); var document = module.Value.First(); var @class = document.Value.First(); var method = @class.Value.First(); Assert.Equal(1, summary.CalculateMethodCoverage(module.Value).Percent); Assert.Equal(1, summary.CalculateMethodCoverage(document.Value).Percent); Assert.Equal(1, summary.CalculateMethodCoverage(@class.Value).Percent); Assert.Equal(1, summary.CalculateMethodCoverage(method.Value.Lines).Percent); }
public void TestCalculateBranchCoveragePercentage_ArithmeticPrecisionCheck() { CoverageSummary summary = new CoverageSummary(); var module = _moduleArithmeticPrecision.First(); var document = module.Value.First(); var @class = document.Value.First(); var method = @class.Value.First(); Assert.Equal(16.66, summary.CalculateBranchCoverage(module.Value).Percent); Assert.Equal(16.66, summary.CalculateBranchCoverage(document.Value).Percent); Assert.Equal(16.66, summary.CalculateBranchCoverage(@class.Value).Percent); Assert.Equal(16.66, summary.CalculateBranchCoverage(method.Value.Branches).Percent); }
void SetModuleCoverage() { var modulesCoverage = new Dictionary <string, CoverageSummary>(); var summary = new CoverletCoverageSummary(); foreach (var moduleInfo in result.Modules) { var moduleLineCoverage = summary.CalculateLineCoverage(moduleInfo.Value); var moduleBranchCoverage = summary.CalculateBranchCoverage(moduleInfo.Value); var moduleName = moduleInfo.Key.Replace(".dll", ""); modulesCoverage[moduleName] = new CoverageSummary(moduleLineCoverage.Percent, moduleBranchCoverage.Percent); } ModuleCoverage = modulesCoverage; }
public void TestCalculateMethodCoverage_SingleModule() { CoverageSummary summary = new CoverageSummary(); var module = _averageCalculationSingleModule.First(); var document = module.Value.First(); var @class = document.Value.First(); var method = @class.Value.First(); Assert.Equal(100, summary.CalculateMethodCoverage(_averageCalculationSingleModule).AverageModulePercent); Assert.Equal(100, summary.CalculateMethodCoverage(module.Value).Percent); Assert.Equal(100, summary.CalculateMethodCoverage(document.Value).Percent); Assert.Equal(100, summary.CalculateMethodCoverage(@class.Value).Percent); Assert.Equal(100, summary.CalculateMethodCoverage(method.Value.Lines).Percent); }
public void TestCalculateBranchCoveragePercentage_ArithmeticPrecisionCheck() { var summary = new CoverageSummary(); System.Collections.Generic.KeyValuePair <string, Documents> module = _moduleArithmeticPrecision.First(); System.Collections.Generic.KeyValuePair <string, Classes> document = module.Value.First(); System.Collections.Generic.KeyValuePair <string, Methods> @class = document.Value.First(); System.Collections.Generic.KeyValuePair <string, Method> method = @class.Value.First(); Assert.Equal(16.66, summary.CalculateBranchCoverage(_moduleArithmeticPrecision).AverageModulePercent); Assert.Equal(16.66, summary.CalculateBranchCoverage(module.Value).Percent); Assert.Equal(16.66, summary.CalculateBranchCoverage(document.Value).Percent); Assert.Equal(16.66, summary.CalculateBranchCoverage(@class.Value).Percent); Assert.Equal(16.66, summary.CalculateBranchCoverage(method.Value.Branches).Percent); }
public void TestCalculateMethodCoverage_SingleModule() { var summary = new CoverageSummary(); System.Collections.Generic.KeyValuePair <string, Documents> module = _averageCalculationSingleModule.First(); System.Collections.Generic.KeyValuePair <string, Classes> document = module.Value.First(); System.Collections.Generic.KeyValuePair <string, Methods> @class = document.Value.First(); System.Collections.Generic.KeyValuePair <string, Method> method = @class.Value.First(); Assert.Equal(100, summary.CalculateMethodCoverage(_averageCalculationSingleModule).AverageModulePercent); Assert.Equal(100, summary.CalculateMethodCoverage(module.Value).Percent); Assert.Equal(100, summary.CalculateMethodCoverage(document.Value).Percent); Assert.Equal(100, summary.CalculateMethodCoverage(@class.Value).Percent); Assert.Equal(100, summary.CalculateMethodCoverage(method.Value.Lines).Percent); }
public void ParseAndPublishCoverageWillPublishSummary() { var token = new CancellationToken(); var processor = new CoverageProcessor(_mockPublisher.Object, _mockTelemetryDataCollector.Object); var summary = new CoverageSummary(); summary.AddCoverageStatistics("", 0, 0, CoverageSummary.Priority.Class); _mockPublisher.Setup(x => x.IsFileCoverageJsonSupported()).Returns(false); _mockParser.Setup(x => x.GetCoverageSummary()).Returns(summary); processor.ParseAndPublishCoverage(_config, token, _mockParser.Object).Wait(); _mockPublisher.Verify(x => x.PublishCoverageSummary( It.Is <CoverageSummary>(a => a == summary), It.Is <CancellationToken>(b => b == token))); }
public string Report(CoverageResult result) { // Calculate coverage var summary = new CoverageSummary(); var overallLineCoverage = summary.CalculateLineCoverage(result.Modules); var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules); var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules); // Report coverage var stringBuilder = new StringBuilder(); OutputLineCoverage(overallLineCoverage, stringBuilder); OutputBranchCoverage(overallBranchCoverage, stringBuilder); OutputMethodCoverage(overallMethodCoverage, stringBuilder); // Return a placeholder return(stringBuilder.ToString()); }
public async Task PublishCoverageSummary(CoverageSummary coverageSummary, CancellationToken cancellationToken) { var coverageData = coverageSummary.CodeCoverageData; var coverageStats = coverageData?.CoverageStats; if (coverageData != null && coverageStats != null && coverageStats.Count() > 0) { // log coverage stats TraceLogger.Info(Resources.PublishingCodeCoverageSummary); foreach (var coverage in coverageStats) { TraceLogger.Info(string.Format(Resources.CoveredStats, coverage.Label, coverage.Covered, coverage.Total)); } try { var uploadToTcm = _featureFlagHelper.GetFeatureFlagState(Constants.FeatureFlags.EnablePublishToTcmServiceDirectlyFromTaskFF, false); _executionContext.TelemetryDataCollector.AddOrUpdate("UploadToTcm", uploadToTcm.ToString()); // Upload to tcm/tfs based on feature flag if (uploadToTcm) { TestResultsHttpClient tcmClient = _clientFactory.GetClient <TestResultsHttpClient>(); using (new SimpleTimer("AzurePipelinesPublisher", "UploadSummary", _executionContext.TelemetryDataCollector)) { await tcmClient.UpdateCodeCoverageSummaryAsync(coverageData, _executionContext.ProjectId, _executionContext.BuildId, cancellationToken : cancellationToken); } } else { TestManagementHttpClient tfsClient = _clientFactory.GetClient <TestManagementHttpClient>(); using (new SimpleTimer("AzurePipelinesPublisher", "UploadSummary", _executionContext.TelemetryDataCollector)) { await tfsClient.UpdateCodeCoverageSummaryAsync(coverageData, _executionContext.ProjectId, _executionContext.BuildId, cancellationToken : cancellationToken); } } } catch (Exception ex) { TraceLogger.Error(string.Format(Resources.FailedtoUploadCoverageSummary, ex.ToString())); } } }
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 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 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); }
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 string Report(CoverageResult result, ISourceRootTranslator sourceRootTranslator) { if (result.Parameters.DeterministicReport) { throw new NotSupportedException("Deterministic report not supported by teamcity reporter"); } // Calculate coverage var summary = new CoverageSummary(); var overallLineCoverage = summary.CalculateLineCoverage(result.Modules); var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules); var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules); // Report coverage var stringBuilder = new StringBuilder(); OutputLineCoverage(overallLineCoverage, stringBuilder); OutputBranchCoverage(overallBranchCoverage, stringBuilder); OutputMethodCoverage(overallMethodCoverage, stringBuilder); // Return a placeholder return(stringBuilder.ToString()); }
public CoverageSummary GetCoverageSummary(List <string> coverageFiles) { var parseResult = ParseCoverageFiles(coverageFiles); var summary = new CoverageSummary(); int totalLines = 0; int coveredLines = 0; foreach (var assembly in parseResult.Assemblies) { foreach (var @class in assembly.Classes) { foreach (var file in @class.Files) { totalLines += file.CoverableLines; coveredLines += file.CoveredLines; } } } summary.AddCoverageStatistics("line", totalLines, coveredLines, CoverageSummary.Priority.Line); return(summary); }
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); 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); }
static int Main(string[] args) { var logger = new ConsoleLogger(); var app = new CommandLineApplication(); app.Name = "coverlet"; app.FullName = "Cross platform .NET Core code coverage tool"; app.HelpOption("-h|--help"); app.VersionOption("-v|--version", GetAssemblyVersion()); CommandArgument module = app.Argument("<ASSEMBLY>", "Path to the test assembly."); CommandOption target = app.Option("-t|--target", "Path to the test runner application.", CommandOptionType.SingleValue); CommandOption targs = app.Option("-a|--targetargs", "Arguments to be passed to the test runner.", CommandOptionType.SingleValue); CommandOption output = app.Option("-o|--output", "Output of the generated coverage report", CommandOptionType.SingleValue); CommandOption formats = app.Option("-f|--format", "Format of the generated coverage report.", CommandOptionType.MultipleValue); CommandOption threshold = app.Option("--threshold", "Exits with error if the coverage % is below value.", CommandOptionType.SingleValue); CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", CommandOptionType.MultipleValue); CommandOption excludeFilters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); CommandOption includeFilters = app.Option("--include", "Filter expressions to include only specific modules and types.", CommandOptionType.MultipleValue); CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); app.OnExecute(() => { if (string.IsNullOrEmpty(module.Value) || string.IsNullOrWhiteSpace(module.Value)) { throw new CommandParsingException(app, "No test assembly specified."); } if (!target.HasValue()) { throw new CommandParsingException(app, "Target must be specified."); } Coverage coverage = new Coverage(module.Value, excludeFilters.Values.ToArray(), includeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), mergeWith.Value()); coverage.PrepareModules(); Process process = new Process(); process.StartInfo.FileName = target.Value(); process.StartInfo.Arguments = targs.HasValue() ? targs.Value() : string.Empty; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.Start(); logger.LogInformation(process.StandardOutput.ReadToEnd()); process.WaitForExit(); var dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); var dThreshold = threshold.HasValue() ? int.Parse(threshold.Value()) : 0; var dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List <string>(new string[] { "line", "branch", "method" }); logger.LogInformation("\nCalculating coverage result..."); var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(dOutput); if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } foreach (var format in (formats.HasValue() ? formats.Values : new List <string>(new string[] { "json" }))) { var reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); } if (reporter.OutputType == ReporterOutputType.Console) { // Output to console logger.LogInformation(" Outputting results to console"); logger.LogInformation(reporter.Report(result)); } else { // Output to file var filename = Path.GetFileName(dOutput); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); logger.LogInformation($" Generating report '{report}'"); File.WriteAllText(report, reporter.Report(result)); } } var summary = new CoverageSummary(); var exceptionBuilder = new StringBuilder(); var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var thresholdFailed = false; var overallLineCoverage = summary.CalculateLineCoverage(result.Modules); var overallBranchCoverage = summary.CalculateBranchCoverage(result.Modules); var overallMethodCoverage = summary.CalculateMethodCoverage(result.Modules); foreach (var _module in result.Modules) { var linePercent = summary.CalculateLineCoverage(_module.Value).Percent * 100; var branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent * 100; var methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent * 100; coverageTable.AddRow(Path.GetFileNameWithoutExtension(_module.Key), $"{linePercent}%", $"{branchPercent}%", $"{methodPercent}%"); if (dThreshold > 0) { if (linePercent < dThreshold && dThresholdTypes.Contains("line")) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(_module.Key)}' has a line coverage '{linePercent}%' below specified threshold '{dThreshold}%'"); thresholdFailed = true; } if (branchPercent < dThreshold && dThresholdTypes.Contains("branch")) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(_module.Key)}' has a branch coverage '{branchPercent}%' below specified threshold '{dThreshold}%'"); thresholdFailed = true; } if (methodPercent < dThreshold && dThresholdTypes.Contains("method")) { exceptionBuilder.AppendLine($"'{Path.GetFileNameWithoutExtension(_module.Key)}' has a method coverage '{methodPercent}%' below specified threshold '{dThreshold}%'"); thresholdFailed = true; } } } logger.LogInformation(string.Empty); logger.LogInformation(coverageTable.ToStringAlternative()); logger.LogInformation($"Total Line: {overallLineCoverage.Percent * 100}%"); logger.LogInformation($"Total Branch: {overallBranchCoverage.Percent * 100}%"); logger.LogInformation($"Total Method: {overallMethodCoverage.Percent * 100}%"); if (thresholdFailed) { throw new Exception(exceptionBuilder.ToString().TrimEnd(Environment.NewLine.ToCharArray())); } return(process.ExitCode == 0 ? 0 : process.ExitCode); }); try { return(app.Execute(args)); } catch (CommandParsingException ex) { logger.LogError(ex.Message); app.ShowHelp(); return(1); } catch (Exception ex) { logger.LogError(ex.Message); return(1); } }
static int Main(string[] args) { var logger = new ConsoleLogger(); var app = new CommandLineApplication(); app.Name = "coverlet"; app.FullName = "Cross platform .NET Core code coverage tool"; app.HelpOption("-h|--help"); app.VersionOption("-v|--version", GetAssemblyVersion()); int exitCode = (int)CommandExitCodes.Success; CommandArgument module = app.Argument("<ASSEMBLY>", "Path to the test assembly."); CommandOption target = app.Option("-t|--target", "Path to the test runner application.", CommandOptionType.SingleValue); CommandOption targs = app.Option("-a|--targetargs", "Arguments to be passed to the test runner.", CommandOptionType.SingleValue); CommandOption output = app.Option("-o|--output", "Output of the generated coverage report", CommandOptionType.SingleValue); CommandOption <LogLevel> verbosity = app.Option <LogLevel>("-v|--verbosity", "Sets the verbosity level of the command. Allowed values are quiet, minimal, normal, detailed.", CommandOptionType.SingleValue); CommandOption formats = app.Option("-f|--format", "Format of the generated coverage report.", CommandOptionType.MultipleValue); CommandOption threshold = app.Option("--threshold", "Exits with error if the coverage % is below value.", CommandOptionType.SingleValue); CommandOption thresholdTypes = app.Option("--threshold-type", "Coverage type to apply the threshold to.", CommandOptionType.MultipleValue); CommandOption thresholdStat = app.Option("--threshold-stat", "Coverage statistic used to enforce the threshold value.", CommandOptionType.SingleValue); CommandOption excludeFilters = app.Option("--exclude", "Filter expressions to exclude specific modules and types.", CommandOptionType.MultipleValue); CommandOption includeFilters = app.Option("--include", "Filter expressions to include only specific modules and types.", CommandOptionType.MultipleValue); CommandOption excludedSourceFiles = app.Option("--exclude-by-file", "Glob patterns specifying source files to exclude.", CommandOptionType.MultipleValue); CommandOption includeDirectories = app.Option("--include-directory", "Include directories containing additional assemblies to be instrumented.", CommandOptionType.MultipleValue); CommandOption excludeAttributes = app.Option("--exclude-by-attribute", "Attributes to exclude from code coverage.", CommandOptionType.MultipleValue); CommandOption includeTestAssembly = app.Option("--include-test-assembly", "Specifies whether to report code coverage of the test assembly.", CommandOptionType.NoValue); CommandOption singleHit = app.Option("--single-hit", "Specifies whether to limit code coverage hit reporting to a single hit for each location", CommandOptionType.NoValue); CommandOption mergeWith = app.Option("--merge-with", "Path to existing coverage result to merge.", CommandOptionType.SingleValue); CommandOption useSourceLink = app.Option("--use-source-link", "Specifies whether to use SourceLink URIs in place of file system paths.", CommandOptionType.NoValue); app.OnExecute(() => { if (string.IsNullOrEmpty(module.Value) || string.IsNullOrWhiteSpace(module.Value)) { throw new CommandParsingException(app, "No test assembly specified."); } if (!target.HasValue()) { throw new CommandParsingException(app, "Target must be specified."); } if (verbosity.HasValue()) { // Adjust log level based on user input. logger.Level = verbosity.ParsedValue; } // We add default exclusion filter if no specified if (excludeFilters.Values.Count == 0) { excludeFilters.Values.Add("[xunit*]*"); } Coverage coverage = new Coverage(module.Value, includeFilters.Values.ToArray(), includeDirectories.Values.ToArray(), excludeFilters.Values.ToArray(), excludedSourceFiles.Values.ToArray(), excludeAttributes.Values.ToArray(), includeTestAssembly.HasValue(), singleHit.HasValue(), mergeWith.Value(), useSourceLink.HasValue(), logger); coverage.PrepareModules(); Process process = new Process(); process.StartInfo.FileName = target.Value(); process.StartInfo.Arguments = targs.HasValue() ? targs.Value() : string.Empty; process.StartInfo.CreateNoWindow = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.OutputDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { logger.LogInformation(eventArgs.Data, important: true); } }; process.ErrorDataReceived += (sender, eventArgs) => { if (!string.IsNullOrEmpty(eventArgs.Data)) { logger.LogError(eventArgs.Data); } }; process.Start(); process.BeginErrorReadLine(); process.BeginOutputReadLine(); process.WaitForExit(); var dOutput = output.HasValue() ? output.Value() : Directory.GetCurrentDirectory() + Path.DirectorySeparatorChar.ToString(); var dThreshold = threshold.HasValue() ? double.Parse(threshold.Value()) : 0; var dThresholdTypes = thresholdTypes.HasValue() ? thresholdTypes.Values : new List <string>(new string[] { "line", "branch", "method" }); var dThresholdStat = thresholdStat.HasValue() ? Enum.Parse <ThresholdStatistic>(thresholdStat.Value(), true) : Enum.Parse <ThresholdStatistic>("minimum", true); logger.LogInformation("\nCalculating coverage result..."); var result = coverage.GetCoverageResult(); var directory = Path.GetDirectoryName(dOutput); if (directory == string.Empty) { directory = Directory.GetCurrentDirectory(); } else if (!Directory.Exists(directory)) { Directory.CreateDirectory(directory); } foreach (var format in (formats.HasValue() ? formats.Values : new List <string>(new string[] { "json" }))) { var reporter = new ReporterFactory(format).CreateReporter(); if (reporter == null) { throw new Exception($"Specified output format '{format}' is not supported"); } if (reporter.OutputType == ReporterOutputType.Console) { // Output to console logger.LogInformation(" Outputting results to console", important: true); logger.LogInformation(reporter.Report(result), important: true); } else { // Output to file var filename = Path.GetFileName(dOutput); filename = (filename == string.Empty) ? $"coverage.{reporter.Extension}" : filename; filename = Path.HasExtension(filename) ? filename : $"{filename}.{reporter.Extension}"; var report = Path.Combine(directory, filename); logger.LogInformation($" Generating report '{report}'", important: true); File.WriteAllText(report, reporter.Report(result)); } } var thresholdTypeFlags = ThresholdTypeFlags.None; foreach (var thresholdType in dThresholdTypes) { if (thresholdType.Equals("line", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlags |= ThresholdTypeFlags.Line; } else if (thresholdType.Equals("branch", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlags |= ThresholdTypeFlags.Branch; } else if (thresholdType.Equals("method", StringComparison.OrdinalIgnoreCase)) { thresholdTypeFlags |= ThresholdTypeFlags.Method; } } var coverageTable = new ConsoleTable("Module", "Line", "Branch", "Method"); var summary = new CoverageSummary(); int numModules = result.Modules.Count; var totalLinePercent = summary.CalculateLineCoverage(result.Modules).Percent; var totalBranchPercent = summary.CalculateBranchCoverage(result.Modules).Percent; var totalMethodPercent = summary.CalculateMethodCoverage(result.Modules).Percent; foreach (var _module in result.Modules) { var linePercent = summary.CalculateLineCoverage(_module.Value).Percent; var branchPercent = summary.CalculateBranchCoverage(_module.Value).Percent; var methodPercent = summary.CalculateMethodCoverage(_module.Value).Percent; coverageTable.AddRow(Path.GetFileNameWithoutExtension(_module.Key), $"{linePercent}%", $"{branchPercent}%", $"{methodPercent}%"); } logger.LogInformation(coverageTable.ToStringAlternative()); coverageTable.Columns.Clear(); coverageTable.Rows.Clear(); coverageTable.AddColumn(new[] { "", "Line", "Branch", "Method" }); coverageTable.AddRow("Total", $"{totalLinePercent}%", $"{totalBranchPercent}%", $"{totalMethodPercent}%"); coverageTable.AddRow("Average", $"{totalLinePercent / numModules}%", $"{totalBranchPercent / numModules}%", $"{totalMethodPercent / numModules}%"); logger.LogInformation(coverageTable.ToStringAlternative()); if (process.ExitCode > 0) { exitCode += (int)CommandExitCodes.TestFailed; } thresholdTypeFlags = result.GetThresholdTypesBelowThreshold(summary, dThreshold, thresholdTypeFlags, dThresholdStat); if (thresholdTypeFlags != ThresholdTypeFlags.None) { exitCode += (int)CommandExitCodes.CoverageBelowThreshold; var exceptionMessageBuilder = new StringBuilder(); if ((thresholdTypeFlags & ThresholdTypeFlags.Line) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} line coverage is below the specified {dThreshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Branch) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} branch coverage is below the specified {dThreshold}"); } if ((thresholdTypeFlags & ThresholdTypeFlags.Method) != ThresholdTypeFlags.None) { exceptionMessageBuilder.AppendLine($"The {dThresholdStat.ToString().ToLower()} method coverage is below the specified {dThreshold}"); } throw new Exception(exceptionMessageBuilder.ToString()); } return(exitCode); }); try { return(app.Execute(args)); } catch (CommandParsingException ex) { logger.LogError(ex.Message); app.ShowHelp(); return((int)CommandExitCodes.CommandParsingException); } catch (Exception ex) { logger.LogError(ex.Message); return(exitCode > 0 ? exitCode : (int)CommandExitCodes.Exception); } }
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 / 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"); var rootDirs = GetRootDirs(result.Modules, result.UseSourceLink).ToList(); rootDirs.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)); @class.Add(new XAttribute("filename", GetRelativePathFromBase(rootDirs, document.Key, result.UseSourceLink))); @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))); 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())); }