public static CoverturaReportComparison Compare(CoverturaReport oldReport, CoverturaReport newReport) { var comparison = new CoverturaReportComparison { Old = oldReport, New = newReport, LineCoverageChange = decimal.Round(newReport.LineRate - oldReport.LineRate, decimals: 2), BranchCoverageChange = decimal.Round(newReport.BranchRate - oldReport.BranchRate, decimals: 2), ComplexityChange = newReport.Complexity - oldReport.Complexity, }; var matchedPackages = new Dictionary <string, CoverturaReportComparison.PackageChanges>(); foreach (var kvp in oldReport.Packages) { var oldPackage = kvp.Value; // try and find package in other report if (!newReport.Packages.TryGetValue(kvp.Key, out var newPackage)) { // package was removed comparison.RemovedPackages.Add(oldPackage); continue; } // do class-level comparison var changes = new CoverturaReportComparison.PackageChanges { Old = oldPackage, New = newPackage, LineCoverageChange = decimal.Round(newPackage.LineRate - oldPackage.LineRate, decimals: 2), BranchCoverageChange = decimal.Round(newPackage.BranchRate - oldPackage.BranchRate, decimals: 2), ComplexityChange = newPackage.Complexity - oldPackage.Complexity, }; foreach (var classKvp in oldPackage.Classes) { var oldClass = classKvp.Value; if (!newPackage.Classes.TryGetValue(classKvp.Key, out var newClass)) { changes.RemovedClasses.Add(oldClass); continue; } var changeSummary = new CoverturaReportComparison.ClassChanges { Name = oldClass.Name, Filename = oldClass.Filename, LineCoverageChange = decimal.Round(newClass.LineRate - oldClass.LineRate, decimals: 2), BranchCoverageChange = decimal.Round(newClass.BranchRate - oldClass.BranchRate, decimals: 2), ComplexityChange = newClass.Complexity - oldClass.Complexity, }; changeSummary.IsSignificantChange = Math.Abs(changeSummary.LineCoverageChange) > SignificantChangeThreshold || Math.Abs(changeSummary.BranchCoverageChange) > SignificantChangeThreshold; changes.ClassChanges[classKvp.Key] = changeSummary; } changes.NewClasses = newPackage.Classes .Where(kvp => !oldPackage.Classes.ContainsKey(kvp.Key)) .Select(kvp => kvp.Value) .ToList(); matchedPackages.Add(kvp.Key, changes); } comparison.MatchedPackages = matchedPackages.Values.ToList(); comparison.NewPackages = newReport.Packages .Where(kvp => !matchedPackages.ContainsKey(kvp.Key)) .Select(kvp => kvp.Value) .ToList(); return(comparison); }
public static string RenderAsMarkdown( string repositoryName, CoverturaReportComparison comparison, int prNumber, string oldDownloadLink, string newDownloadLink, string oldReportLink, string newReportLink, string oldCommit, string newCommit) { var oldBranchMarkdown = $"[master](https://github.com/DataDog/{repositoryName}/tree/{oldCommit})"; var newBranchMarkdown = $"#{prNumber}"; var prFiles = $"https://github.com/DataDog/{repositoryName}/pull/{prNumber}/files"; var tree = $"https://github.com/DataDog/{repositoryName}/tree/{newCommit}"; var oldReport = comparison.Old; var newReport = comparison.New; var sb = new StringBuilder($@"## Code Coverage Report :bar_chart: {GetIcon(comparison.LineCoverageChange)} Merging {newBranchMarkdown} into {oldBranchMarkdown} will {GetDescription(comparison.LineCoverageChange, "line coverage")} {GetIcon(comparison.BranchCoverageChange)} Merging {newBranchMarkdown} into {oldBranchMarkdown} will {GetDescription(comparison.BranchCoverageChange, "branch coverage")} {GetIcon(-comparison.ComplexityChange)} Merging {newBranchMarkdown} into {oldBranchMarkdown} will {GetComplexityDescription(comparison.ComplexityChange)} | | {oldBranchMarkdown} | {newBranchMarkdown} | Change | |:----------|:-----------:|:-----------:|:--------:| | Lines | `{oldReport.LinesCovered}` / `{oldReport.LinesValid}` | `{newReport.LinesCovered}` / `{newReport.LinesValid}` | | | Lines % | `{oldReport.LineRate:P0}` | `{newReport.LineRate:P0}` | `{comparison.LineCoverageChange:P0}` {GetIcon(comparison.LineCoverageChange)} | | Branches | `{oldReport.BranchesCovered}` / `{oldReport.BranchesValid}` | `{newReport.BranchesCovered}` / `{newReport.BranchesValid}` | | | Branches %| `{oldReport.BranchRate:P0}` | `{newReport.BranchRate:P0}` | `{comparison.BranchCoverageChange:P0}` {GetIcon(comparison.BranchCoverageChange)} | | Complexity| `{oldReport.Complexity}` | `{newReport.Complexity}` | `{comparison.ComplexityChange}` {GetIcon(-comparison.ComplexityChange)} | View the full report for further details: * [HTML report for master]({oldReportLink}) | [Source file]({oldDownloadLink}) * [HTML report for this PR #{prNumber}]({newReportLink}) | [Source file]({newDownloadLink}) "); foreach (var package in comparison.MatchedPackages) { sb.Append($@"### {package.New.Name} Breakdown {GetIcon(package.LineCoverageChange)} | | {oldBranchMarkdown} | {newBranchMarkdown} | Change | |:-------|:-----------:|:-----------:|:--------:| | Lines %| `{package.Old.LineRate:P0}` | `{package.New.LineRate:P0}` | `{package.LineCoverageChange:P0}` {GetIcon(package.LineCoverageChange)} | | Branches %| `{package.Old.BranchRate:P0}` | `{package.New.BranchRate:P0}` | `{package.BranchCoverageChange:P0}` {GetIcon(package.BranchCoverageChange)} | | Complexity| `{package.Old.Complexity}` | `{package.New.Complexity}` | `{package.ComplexityChange}` {GetIcon(-package.ComplexityChange)} | "); var changes = package.ClassChanges.Values .Where(change => !IgnoredClassPrefixes.Any(toIgnore => change.Name.StartsWith(toIgnore))) .ToList(); if (changes.Any()) { sb.Append($@" The following classes have significant coverage changes. | File | Line coverage change | Branch coverage change | Complexity change | |:--------|:--------------------:|:----------:|:--------:|"); var maxFileDisplay = 10; var significantChanges = changes .Where(x => x.IsSignificantChange) .OrderBy(x => x.LineCoverageChange) .ThenBy(x => x.BranchCoverageChange) .ThenBy(x => x.Name) .ToList(); foreach (var classChange in significantChanges.Take(maxFileDisplay)) { var change = classChange; sb.Append($@" | [{change.Name}]({FixFilename(change.Filename)}) | `{change.LineCoverageChange:P0}` {GetIcon(change.LineCoverageChange)} | `{change.BranchCoverageChange:P0}` {GetIcon(change.BranchCoverageChange)} | `{change.ComplexityChange}` {GetIcon(-change.ComplexityChange)} |"); } var extras = significantChanges.Count - maxFileDisplay; if (extras > 0) { sb.Append($@" | ...And {extras} more | | | |"); } sb.AppendLine().AppendLine(); } if (package.NewClasses.Any()) { sb.Append($@" The following classes were added in {newBranchMarkdown}: | File | Line coverage | Branch coverage | Complexity | |:--------|:--------------------:|:----------:|:--------:|"); var maxFileDisplay = 5; foreach (var newClass in package.NewClasses.Take(maxFileDisplay)) { sb.Append($@" | [{FixClassName(newClass.Name)}]({prFiles}) | `{newClass.LineRate:P0}` | `{newClass.BranchRate:P0}` | `{newClass.Complexity}` |"); } var extras = package.NewClasses.Count - maxFileDisplay; if (extras > 0) { sb.Append($@" | ...And {extras} more | | | |"); } sb.AppendLine().AppendLine(); } if (package.RemovedClasses.Any()) { sb.AppendLine($@" {package.RemovedClasses.Count} classes were removed from {package.New.Name} in {newBranchMarkdown}") .AppendLine(); } } if (comparison.NewPackages.Any()) { sb.Append($@"### New projects {comparison.NewPackages.Count} were added in {newBranchMarkdown}: | Project | Line coverage | Branch coverage | Complexity | |:--------|:--------------------:|:----------:|:--------:|"); var maxDisplay = 5; foreach (var newProject in comparison.NewPackages.Take(maxDisplay)) { sb.Append($@" | [{newProject.Name}]({prFiles}) | `{newProject.LineRate:P0}` | `{newProject.BranchRate:P0}` | `{newProject.Complexity}` |"); } var extras = comparison.NewPackages.Count - maxDisplay; if (extras > 0) { sb.Append($@" | ...And {extras} more | | | |"); } } if (comparison.RemovedPackages.Any()) { sb.AppendLine($@"### Deleted projects {comparison.RemovedPackages.Count} projects were removed in {newBranchMarkdown} | Project | Line coverage | Branch coverage | Complexity | |:--------|:--------------------:|:----------:|:--------:|"); var maxDisplay = 5; foreach (var oldProject in comparison.RemovedPackages.Take(maxDisplay)) { sb.Append($@" | [{oldProject.Name}]({prFiles}) | `{oldProject.LineRate:P0}` | `{oldProject.BranchRate:P0}` | `{oldProject.Complexity}` |"); } var extras = comparison.RemovedPackages.Count - maxDisplay; if (extras > 0) { sb.Append($@" | ...And {extras} more | | | |"); } } sb.AppendLine($@" View the full reports for further details: * [HTML report for master]({oldReportLink}) | [Source file]({oldDownloadLink}) * [HTML report for this PR #{prNumber}]({newReportLink}) | [Source file]({newDownloadLink})");