1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.report.html.page_stats; 11 12 import logger = std.experimental.logger; 13 import std.datetime : Clock, dur; 14 import std.format : format; 15 16 import arsd.dom : Document, Element, require, Table, RawSource; 17 18 import dextool.plugin.mutate.backend.database : Database; 19 import dextool.plugin.mutate.backend.report.analyzers : MutationStat, TestCaseDeadStat, 20 TestCaseOverlapStat, reportStatistics, reportDeadTestCases, reportTestCaseFullOverlap; 21 import dextool.plugin.mutate.backend.report.html.constants; 22 import dextool.plugin.mutate.backend.report.html.js; 23 import dextool.plugin.mutate.backend.report.html.tmpl : tmplBasicPage, tmplDefaultTable; 24 import dextool.plugin.mutate.backend.type : Mutation; 25 import dextool.plugin.mutate.config : ConfigReport; 26 import dextool.plugin.mutate.type : MutationKind; 27 28 string makeStats(ref Database db, ref const ConfigReport conf, 29 const(MutationKind)[] humanReadableKinds, const(Mutation.Kind)[] kinds) @trusted { 30 import dextool.plugin.mutate.type : ReportSection; 31 import dextool.set; 32 33 auto sections = conf.reportSection.toSet; 34 35 auto doc = tmplBasicPage; 36 37 auto s = doc.root.childElements("head")[0].addChild("script"); 38 s.addChild(new RawSource(doc, js_similarity)); 39 40 doc.title(format("Mutation Testing Report %(%s %) %s", humanReadableKinds, Clock.currTime)); 41 doc.mainBody.setAttribute("onload", "init()"); 42 overallStat(reportStatistics(db, kinds), doc.mainBody); 43 if (ReportSection.tc_killed_no_mutants in sections) 44 deadTestCase(reportDeadTestCases(db), doc.mainBody); 45 if (ReportSection.tc_full_overlap in sections 46 || ReportSection.tc_full_overlap_with_mutation_id in sections) 47 overlapTestCase(reportTestCaseFullOverlap(db, kinds), doc.mainBody); 48 49 return doc.toPrettyString; 50 } 51 52 private: 53 54 // TODO: this function contains duplicated logic from the one in ../utility.d 55 void overallStat(const MutationStat s, Element n) { 56 import std.conv : to; 57 import std.typecons : tuple; 58 59 n.addChild("p", 60 "The tables are hidden by default, click on the corresponding header to display a table."); 61 with (n.addChild("button", "expand all")) { 62 setAttribute("type", "button"); 63 setAttribute("id", "expand_all"); 64 } 65 with (n.addChild("button", "collapse all")) { 66 setAttribute("type", "button"); 67 setAttribute("id", "collapse_all"); 68 } 69 70 auto comp_container = n.addChild("div").addClass("comp_container"); 71 auto heading = comp_container.addChild("h2").addClass("tbl_header"); 72 73 comp_container.addChild("p").appendHtml(format("Mutation Score <b>%.3s</b>", s.score)); 74 comp_container.addChild("p", format("Time spent: %s", s.totalTime)); 75 heading.addChild("i").addClass("right"); 76 heading.appendText(" Summary"); 77 if (s.untested > 0 && s.predictedDone > 0.dur!"msecs") { 78 const pred = Clock.currTime + s.predictedDone; 79 comp_container.addChild("p", format("Remaining: %s (%s)", 80 s.predictedDone, pred.toISOExtString)); 81 } 82 83 auto tbl_container = comp_container.addChild("div").addClass("tbl_container"); 84 tbl_container.setAttribute("style", "display: none;"); 85 auto tbl = tmplDefaultTable(tbl_container, ["Type", "Value"]); 86 foreach (const d; [ 87 tuple("Total", s.total), tuple("Untested", s.untested), 88 tuple("Alive", s.alive), tuple("Killed", s.killed), 89 tuple("Timeout", s.timeout), 90 tuple("Killed by compiler", s.killedByCompiler), 91 ]) { 92 tbl.appendRow(d[0], d[1]); 93 } 94 95 if (s.aliveNoMut != 0) { 96 tbl.appendRow("NoMut", s.aliveNoMut.to!string); 97 tbl.appendRow("NoMut/total", format("%.3s", s.suppressedOfTotal)); 98 99 auto p = comp_container.addChild("p", 100 "NoMut is the number of mutants that are alive but ignored."); 101 p.appendHtml(" They are <i>suppressed</i>."); 102 p.appendText(" This result in those mutants increasing the mutation score."); 103 p.appendText(" The suppressed/total is how much it has increased."); 104 p.appendHtml(" You <b>should</b> react if it is high."); 105 } 106 } 107 108 void deadTestCase(const TestCaseDeadStat s, Element n) { 109 if (s.numDeadTC == 0) 110 return; 111 auto comp_container = n.addChild("div").addClass("comp_container"); 112 auto heading = comp_container.addChild("h2").addClass("tbl_header"); 113 heading.addChild("i").addClass("right"); 114 heading.appendText(" Dead Test Cases"); 115 116 comp_container.addChild("p", "These test case have killed zero mutants. There is a high probability that these contain implementation errors. They should be manually inspected."); 117 118 comp_container.addChild("p", format("%s/%s = %s of all test cases", 119 s.numDeadTC, s.total, s.ratio)); 120 auto tbl_container = comp_container.addChild("div").addClass("tbl_container"); 121 tbl_container.setAttribute("style", "display: none;"); 122 auto tbl = tmplDefaultTable(tbl_container, ["Test Case"]); 123 foreach (tc; s.testCases) { 124 tbl.appendRow(tc.name); 125 } 126 } 127 128 void overlapTestCase(const TestCaseOverlapStat s, Element n) { 129 import std.algorithm : sort, map, filter; 130 import std.array : array; 131 import std.conv : to; 132 import std.range : enumerate; 133 134 if (s.total == 0) 135 return; 136 auto comp_container = n.addChild("div").addClass("comp_container"); 137 auto heading = comp_container.addChild("h2").addClass("tbl_header"); 138 heading.addChild("i").addClass("right"); 139 heading.appendText(" Overlapping Test Cases"); 140 141 comp_container.addChild("p", "These test has killed exactly the same mutants. This is an indication that they verify the same aspects. This can mean that some of them may be redundant."); 142 143 comp_container.addChild("p", s.sumToString); 144 145 auto tbl_container = comp_container.addChild("div").addClass("tbl_container"); 146 147 tbl_container.setAttribute("style", "display: none;"); 148 auto tbl = tmplDefaultTable(tbl_container, [ 149 "Test Case", "Count", "Mutation IDs" 150 ]); 151 152 foreach (tcs; s.tc_mut.byKeyValue.filter!(a => a.value.length > 1).enumerate) { 153 bool first = true; 154 string cls = () { 155 if (tcs.index % 2 == 0) 156 return tableRowStyle; 157 return tableRowDarkStyle; 158 }(); 159 160 // TODO this is a bit slow. use a DB row iterator instead. 161 foreach (name; tcs.value.value.map!(id => s.name_tc[id].idup).array.sort) { 162 auto r = tbl.appendRow(); 163 if (first) { 164 r.addChild("td", name).addClass(cls); 165 r.addChild("td", s.mutid_mut[tcs.value.key].length.to!string).addClass(cls); 166 r.addChild("td", format("%(%s %)", s.mutid_mut[tcs.value.key])).addClass(cls); 167 } else { 168 r.addChild("td", name).addClass(cls); 169 } 170 first = false; 171 } 172 } 173 }