diff --git a/src/ReportGenerator.Core/ReportGenerator.Core.csproj b/src/ReportGenerator.Core/ReportGenerator.Core.csproj index 2f4afed6..6f05dd1a 100644 --- a/src/ReportGenerator.Core/ReportGenerator.Core.csproj +++ b/src/ReportGenerator.Core/ReportGenerator.Core.csproj @@ -87,6 +87,7 @@ + all diff --git a/src/ReportGenerator.Core/Reporting/Builders/Rendering/HtmlRenderer.cs b/src/ReportGenerator.Core/Reporting/Builders/Rendering/HtmlRenderer.cs index 7d1309d3..d541903b 100644 --- a/src/ReportGenerator.Core/Reporting/Builders/Rendering/HtmlRenderer.cs +++ b/src/ReportGenerator.Core/Reporting/Builders/Rendering/HtmlRenderer.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; using System.IO; @@ -6,6 +7,7 @@ using System.Net; using System.Text; using System.Text.RegularExpressions; +using Microsoft.Extensions.ObjectPool; using Palmmedia.ReportGenerator.Core.CodeAnalysis; using Palmmedia.ReportGenerator.Core.Common; using Palmmedia.ReportGenerator.Core.Logging; @@ -19,27 +21,6 @@ namespace Palmmedia.ReportGenerator.Core.Reporting.Builders.Rendering /// internal class HtmlRenderer : IHtmlRenderer, IDisposable { - /// - /// The head of each generated HTML file. - /// - private const string HtmlStart = @" - - - - - - -{0} - {1} -{2} -
"; - - /// - /// The end of each generated HTML file. - /// - private const string HtmlEnd = @"
-{0} -"; - /// /// The link to the static CSS file. /// @@ -118,7 +99,7 @@ internal HtmlRenderer( this.fileNameByClass = fileNameByClass; this.onlySummary = onlySummary; this.htmlMode = htmlMode; - this.javaScriptContent = new StringBuilder(); + this.javaScriptContent = StringBuilderCache.Get(); this.cssFileResource = cssFileResource; this.additionalCssFileResources = additionalCssFileResource == null ? new string[0] : new[] { additionalCssFileResource }; } @@ -141,7 +122,7 @@ internal HtmlRenderer( this.fileNameByClass = fileNameByClass; this.onlySummary = onlySummary; this.htmlMode = htmlMode; - this.javaScriptContent = new StringBuilder(); + this.javaScriptContent = StringBuilderCache.Get(); this.additionalCssFileResources = additionalCssFileResources ?? new string[0]; this.cssFileResource = cssFileResource; } @@ -159,14 +140,7 @@ public void BeginSummaryReport(string targetDirectory, string fileName, string t Logger.InfoFormat(Resources.WritingReportFile, targetPath); this.CreateTextWriter(targetPath); - using (var cssStream = this.GetCombinedCss()) - { - string style = this.htmlMode == HtmlMode.InlineCssAndJavaScript ? - "" - : CssLink; - - this.reportTextWriter.WriteLine(HtmlStart, WebUtility.HtmlEncode(title), WebUtility.HtmlEncode(ReportResources.CoverageReport), style); - } + this.WriteHtmlStart(this.reportTextWriter, title, ReportResources.CoverageReport); } /// @@ -179,14 +153,7 @@ public void BeginClassReport(string targetDirectory, Assembly assembly, string c Logger.DebugFormat(Resources.WritingReportFile, targetPath); this.CreateTextWriter(Path.Combine(targetDirectory, targetPath)); - using (var cssStream = this.GetCombinedCss()) - { - string style = this.htmlMode == HtmlMode.InlineCssAndJavaScript ? - "" - : CssLink; - - this.reportTextWriter.WriteLine(HtmlStart, WebUtility.HtmlEncode(classDisplayName), WebUtility.HtmlEncode(additionalTitle + ReportResources.CoverageReport), style); - } + this.WriteHtmlStart(this.reportTextWriter, classDisplayName, additionalTitle + ReportResources.CoverageReport); } /// @@ -425,7 +392,6 @@ public void BeginSummaryTable(bool branchCoverageAvailable, bool methodCoverageA this.reportTextWriter.WriteLine(""); this.reportTextWriter.WriteLine(""); this.reportTextWriter.WriteLine(""); - if (branchCoverageAvailable) { this.reportTextWriter.WriteLine(""); @@ -545,88 +511,92 @@ public void CustomSummary(IEnumerable assemblies, IEnumerable h.CodeElementCoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture))) + "]"; } - var historicCoveragesSb = new StringBuilder(); - int historicCoveragesCounter = 0; - historicCoveragesSb.Append("["); - foreach (var historicCoverage in @class.HistoricCoverages) + void WriteHistoricCoverage() { - historicCoverageExecutionTimes.Add(historicCoverage.ExecutionTime); - tagsByBistoricCoverageExecutionTime[historicCoverage.ExecutionTime] = historicCoverage.Tag; - - if (historicCoveragesCounter++ > 0) + int historicCoveragesCounter = 0; + this.javaScriptContent.Append("["); + foreach (var historicCoverage in @class.HistoricCoverages) { - historicCoveragesSb.Append(", "); - } + historicCoverageExecutionTimes.Add(historicCoverage.ExecutionTime); + tagsByBistoricCoverageExecutionTime[historicCoverage.ExecutionTime] = historicCoverage.Tag; - historicCoveragesSb.AppendFormat( - "{{ \"et\": \"{0} - {1}{2}{3}\", \"cl\": {4}, \"ucl\": {5}, \"cal\": {6}, \"tl\": {7}, \"lcq\": {8}, \"cb\": {9}, \"tb\": {10}, \"bcq\": {11}, \"cm\": {12}, \"tm\": {13}, \"mcq\": {14} }}", - historicCoverage.ExecutionTime.ToShortDateString(), - historicCoverage.ExecutionTime.ToLongTimeString(), - string.IsNullOrEmpty(historicCoverage.Tag) ? string.Empty : " - ", - historicCoverage.Tag, - historicCoverage.CoveredLines.ToString(CultureInfo.InvariantCulture), - (historicCoverage.CoverableLines - historicCoverage.CoveredLines).ToString(CultureInfo.InvariantCulture), - historicCoverage.CoverableLines.ToString(CultureInfo.InvariantCulture), - historicCoverage.TotalLines.ToString(CultureInfo.InvariantCulture), - historicCoverage.CoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture), - historicCoverage.CoveredBranches.ToString(CultureInfo.InvariantCulture), - historicCoverage.TotalBranches.ToString(CultureInfo.InvariantCulture), - historicCoverage.BranchCoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture), - methodCoverageAvailable ? historicCoverage.CoveredCodeElements.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "0", - methodCoverageAvailable ? historicCoverage.TotalCodeElements.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "0", - methodCoverageAvailable ? historicCoverage.CodeElementCoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "0"); - } + if (historicCoveragesCounter++ > 0) + { + this.javaScriptContent.Append(", "); + } - historicCoveragesSb.Append("]"); + this.javaScriptContent.AppendFormat( + "{{ \"et\": \"{0} - {1}{2}{3}\", \"cl\": {4}, \"ucl\": {5}, \"cal\": {6}, \"tl\": {7}, \"lcq\": {8}, \"cb\": {9}, \"tb\": {10}, \"bcq\": {11}, \"cm\": {12}, \"tm\": {13}, \"mcq\": {14} }}", + historicCoverage.ExecutionTime.ToShortDateString(), + historicCoverage.ExecutionTime.ToLongTimeString(), + string.IsNullOrEmpty(historicCoverage.Tag) ? string.Empty : " - ", + historicCoverage.Tag, + historicCoverage.CoveredLines.ToString(CultureInfo.InvariantCulture), + (historicCoverage.CoverableLines - historicCoverage.CoveredLines).ToString(CultureInfo.InvariantCulture), + historicCoverage.CoverableLines.ToString(CultureInfo.InvariantCulture), + historicCoverage.TotalLines.ToString(CultureInfo.InvariantCulture), + historicCoverage.CoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture), + historicCoverage.CoveredBranches.ToString(CultureInfo.InvariantCulture), + historicCoverage.TotalBranches.ToString(CultureInfo.InvariantCulture), + historicCoverage.BranchCoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture), + methodCoverageAvailable ? historicCoverage.CoveredCodeElements.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "0", + methodCoverageAvailable ? historicCoverage.TotalCodeElements.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "0", + methodCoverageAvailable ? historicCoverage.CodeElementCoverageQuota.GetValueOrDefault().ToString(CultureInfo.InvariantCulture) : "0"); + } - var metricsSb = new StringBuilder(); - int metricsCounter = 0; - metricsSb.Append("{"); + this.javaScriptContent.Append("]"); + } - foreach (var metricGroup in @class.Files.SelectMany(f => f.MethodMetrics).SelectMany(m => m.Metrics).GroupBy(m => m.Name)) + void WriteMetricsCoverage() { - var firstMetric = metricGroup.First(); - metricsByName[firstMetric.Name] = firstMetric; + int metricsCounter = 0; + this.javaScriptContent.Append("{"); - if (!methodCoverageAvailable) + foreach (var metricGroup in @class.Files.SelectMany(f => f.MethodMetrics).SelectMany(m => m.Metrics).GroupBy(m => m.Name)) { - continue; - } + var firstMetric = metricGroup.First(); + metricsByName[firstMetric.Name] = firstMetric; - decimal? value = null; + if (!methodCoverageAvailable) + { + continue; + } - if (firstMetric.MetricType == MetricType.CoverageAbsolute) - { - value = metricGroup.SafeSum(m => m.Value); - } - else - { - // Show worst result on summary page - if (firstMetric.MergeOrder == MetricMergeOrder.HigherIsBetter) + decimal? value = null; + + if (firstMetric.MetricType == MetricType.CoverageAbsolute) { - value = metricGroup.Min(m => m.Value); + value = metricGroup.SafeSum(m => m.Value); } else { - value = metricGroup.Max(m => m.Value); + // Show worst result on summary page + if (firstMetric.MergeOrder == MetricMergeOrder.HigherIsBetter) + { + value = metricGroup.Min(m => m.Value); + } + else + { + value = metricGroup.Max(m => m.Value); + } } - } - if (value.HasValue) - { - if (metricsCounter++ > 0) + if (value.HasValue) { - metricsSb.Append(", "); + if (metricsCounter++ > 0) + { + this.javaScriptContent.Append(", "); + } + + this.javaScriptContent.AppendFormat( + " \"{0}\": {1}", + firstMetric.Abbreviation, + value.Value.ToString(CultureInfo.InvariantCulture)); } - - metricsSb.AppendFormat( - " \"{0}\": {1}", - firstMetric.Abbreviation, - value.Value.ToString(CultureInfo.InvariantCulture)); } - } - metricsSb.Append(" }"); + this.javaScriptContent.Append(" }"); + } this.javaScriptContent.Append(" { "); this.javaScriptContent.AppendFormat("\"name\": \"{0}\",", @class.DisplayName.Replace(@"\", @"\\")); @@ -645,8 +615,13 @@ public void CustomSummary(IEnumerable assemblies, IEnumerable historicCoverages, bool methodCo id, svgHistory); - var series = new StringBuilder(); - series.Append("["); - if (filteredHistoricCoverages.Any(h => h.CoverageQuota.HasValue)) + void WriteSeries(TextWriter series) { - for (int i = 0; i < filteredHistoricCoverages.Count; i++) + series.Write("["); + if (filteredHistoricCoverages.Any(h => h.CoverageQuota.HasValue)) { - if (i > 0) + for (int i = 0; i < filteredHistoricCoverages.Count; i++) { - series.Append(", "); - } + if (i > 0) + { + series.Write(", "); + } - if (filteredHistoricCoverages[i].CoverageQuota.HasValue) - { - series.Append("{ 'meta': "); - series.Append(i); - series.Append(", 'value': "); - series.Append(filteredHistoricCoverages[i].CoverageQuota.Value.ToString(CultureInfo.InvariantCulture)); - series.Append(" }"); - } - else - { - series.Append("null"); + if (filteredHistoricCoverages[i].CoverageQuota.HasValue) + { + series.Write("{ 'meta': "); + series.Write(i); + series.Write(", 'value': "); + series.Write(filteredHistoricCoverages[i].CoverageQuota.Value.ToString(CultureInfo.InvariantCulture)); + series.Write(" }"); + } + else + { + series.Write("null"); + } } } - } - series.AppendLine("],"); - series.Append("["); + series.WriteLine("],"); + series.Write("["); - if (filteredHistoricCoverages.Any(h => h.BranchCoverageQuota.HasValue)) - { - for (int i = 0; i < filteredHistoricCoverages.Count; i++) + if (filteredHistoricCoverages.Any(h => h.BranchCoverageQuota.HasValue)) { - if (i > 0) + for (int i = 0; i < filteredHistoricCoverages.Count; i++) { - series.Append(", "); - } + if (i > 0) + { + series.Write(", "); + } - if (filteredHistoricCoverages[i].BranchCoverageQuota.HasValue) - { - series.Append("{ 'meta': "); - series.Append(i); - series.Append(", 'value': "); - series.Append(filteredHistoricCoverages[i].BranchCoverageQuota.Value.ToString(CultureInfo.InvariantCulture)); - series.Append(" }"); - } - else - { - series.Append("null"); + if (filteredHistoricCoverages[i].BranchCoverageQuota.HasValue) + { + series.Write("{ 'meta': "); + series.Write(i); + series.Write(", 'value': "); + series.Write(filteredHistoricCoverages[i].BranchCoverageQuota.Value.ToString(CultureInfo.InvariantCulture)); + series.Write(" }"); + } + else + { + series.Write("null"); + } } } - } - series.AppendLine("],"); - series.Append("["); + series.WriteLine("],"); + series.Write("["); - if (methodCoverageAvailable && filteredHistoricCoverages.Any(h => h.CodeElementCoverageQuota.HasValue)) - { - for (int i = 0; i < filteredHistoricCoverages.Count; i++) + if (methodCoverageAvailable && filteredHistoricCoverages.Any(h => h.CodeElementCoverageQuota.HasValue)) { - if (i > 0) + for (int i = 0; i < filteredHistoricCoverages.Count; i++) { - series.Append(", "); - } + if (i > 0) + { + series.Write(", "); + } - if (filteredHistoricCoverages[i].CodeElementCoverageQuota.HasValue) - { - series.Append("{ 'meta': "); - series.Append(i); - series.Append(", 'value': "); - series.Append(filteredHistoricCoverages[i].CodeElementCoverageQuota.Value.ToString(CultureInfo.InvariantCulture)); - series.Append(" }"); - } - else - { - series.Append("null"); + if (filteredHistoricCoverages[i].CodeElementCoverageQuota.HasValue) + { + series.Write("{ 'meta': "); + series.Write(i); + series.Write(", 'value': "); + series.Write(filteredHistoricCoverages[i].CodeElementCoverageQuota.Value.ToString(CultureInfo.InvariantCulture)); + series.Write(" }"); + } + else + { + series.Write("null"); + } } } - } - series.AppendLine("]"); + series.WriteLine("]"); + } var toolTips = filteredHistoricCoverages.Select(h => string.Format( @@ -1146,9 +1123,10 @@ public void Chart(IEnumerable historicCoverages, bool methodCo this.reportTextWriter.WriteLine(""; + this.reportTextWriter.WriteLine(""); if (this.htmlMode == HtmlMode.ExternalCssAndJavaScriptWithQueryStringHandling) { // #349: Apply query string to referenced CSS and JavaScript files and links - javascript = $@""; +"); } else if (this.htmlMode == HtmlMode.InlineCssAndJavaScript) { - using (var javaScriptStream = this.GetCombinedJavascript()) - { - javascript = ""; - } + this.reportTextWriter.Write(""); + } + else + { + this.reportTextWriter.WriteLine($""); } - this.reportTextWriter.Write(HtmlEnd, javascript); + this.reportTextWriter.Write(""); } /// @@ -1954,5 +1906,41 @@ private List FilterHistoricCoverages(IEnumerable Cache = new ConcurrentDictionary(); + + public static string Get(string resourceName) => Cache.GetOrAdd(resourceName, v => + { + using (Stream stream = typeof(HtmlRenderer).Assembly.GetManifestResourceStream("Palmmedia.ReportGenerator.Core.Reporting.Builders.Rendering.resources." + resourceName)) + { + using (var reader = new StreamReader(stream)) + { + return reader.ReadToEnd(); + } + } + }); + } + + private static class StringBuilderCache + { + private static readonly DefaultObjectPool Pool = new DefaultObjectPool(new StringBuilderPooledObjectPolicy + { + InitialCapacity = 4096, + MaximumRetainedCapacity = 1 * 1024 * 1024 + }); + + internal static StringBuilder Get() => Pool.Get(); + + internal static void Return(StringBuilder builder) => Pool.Return(builder); + + internal static string ToStringAndReturnToPool(StringBuilder builder) + { + var result = builder.ToString(); + Pool.Return(builder); + return result; + } + } } }