protected override void WriteDetailedReport(InstrumentationResult result, IDictionary <string, SourceFile> files, Hits hits) { foreach (var kvFile in files) { var lines = File.ReadAllLines(Path.Combine(result.SourcePath, kvFile.Key)); var fileName = GetHtmlFileName(kvFile.Key); Directory.CreateDirectory(Path.GetDirectoryName(fileName)); using (var htmlWriter = (TextWriter)File.CreateText(fileName)) { htmlWriter.WriteLine("<html>"); htmlWriter.WriteLine("<body style=\"font-family: monospace;\">"); var uncoveredLineNumbers = new HashSet <int>(); var coveredLineNumbers = new HashSet <int>(); foreach (var i in kvFile.Value.Instructions) { if (hits.IsInstructionHit(i.Id)) { coveredLineNumbers.UnionWith(i.GetLines()); } else { uncoveredLineNumbers.UnionWith(i.GetLines()); } } var l = 0; foreach (var line in lines) { l++; var style = "white-space: pre;"; if (coveredLineNumbers.Contains(l)) { style += BgColorGreen; } else if (uncoveredLineNumbers.Contains(l)) { style += BgColorRed; } else { style += BgColorBlue; } var instructions = kvFile.Value.Instructions.Where(i => i.GetLines().Contains(l)).ToArray(); var counter = instructions.Sum(a => hits.GetInstructionHitCount(a.Id)); var testMethods = instructions .SelectMany(i => hits.GetInstructionTestMethods(i.Id)) .Distinct() .ToArray(); var testNames = string.Join(", ", testMethods.Select(m => $"{m.ClassName}.{m.MethodName} ({m.Counter})")); var testNamesIcon = testMethods.Length > 0 ? $"<span style=\"cursor: pointer; margin-right: 5px;\" title=\"Covered by tests: {testNames} for {counter}\">ⓘ</span>" : $"<span style=\"margin-right: 5px;\"> </span>"; if (!string.IsNullOrEmpty(line)) { htmlWriter.WriteLine($"<div style=\"{style}\" title=\"{testNames}\">{testNamesIcon}{WebUtility.HtmlEncode(line)}</div>"); } else { htmlWriter.WriteLine($"<div style=\"{style}\" title=\"{testNames}\">{testNamesIcon} </div>"); } } htmlWriter.WriteLine("</body>"); htmlWriter.WriteLine("</html>"); } } }