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; 17 18 import dextool.plugin.mutate.backend.database : Database; 19 import dextool.plugin.mutate.backend.report.html.constants; 20 import dextool.plugin.mutate.backend.report.html.tmpl : tmplBasicPage, tmplDefaultTable; 21 import dextool.plugin.mutate.backend.report.utility; 22 import dextool.plugin.mutate.backend.type : Mutation; 23 import dextool.plugin.mutate.config : ConfigReport; 24 import dextool.plugin.mutate.type : MutationKind; 25 26 string makeStats(ref Database db, ref const ConfigReport conf, 27 const(MutationKind)[] humanReadableKinds, const(Mutation.Kind)[] kinds) @trusted { 28 import dextool.plugin.mutate.type : ReportSection; 29 import dextool.set; 30 31 auto sections = setFromList(conf.reportSection); 32 33 auto doc = tmplBasicPage; 34 doc.title(format("Mutation Testing Report %(%s %) %s", humanReadableKinds, Clock.currTime)); 35 36 overallStat(reportStatistics(db, kinds), doc.mainBody); 37 if (ReportSection.tc_killed_no_mutants in sections) 38 deadTestCase(reportDeadTestCases(db), doc.mainBody); 39 if (ReportSection.tc_full_overlap in sections 40 || ReportSection.tc_full_overlap_with_mutation_id in sections) 41 overlapTestCase(reportTestCaseFullOverlap(db, kinds), doc.mainBody); 42 43 return doc.toPrettyString; 44 } 45 46 private: 47 48 void overallStat(const MutationStat s, Element n) { 49 import std.conv : to; 50 import std.typecons : tuple; 51 52 n.addChild("h2", "Summary"); 53 n.addChild("p").appendHtml(format("Mutation Score <b>%.3s</b>", s.score)); 54 n.addChild("p", format("Execution time %s", s.totalTime)); 55 56 if (s.untested > 0 && s.predictedDone > 0.dur!"msecs") { 57 n.addChild("p", format("Predicted time until mutation testing is done %s (%s)", 58 s.predictedDone, Clock.currTime + s.predictedDone)); 59 } 60 61 auto tbl = tmplDefaultTable(n, ["Type", "Value"]); 62 foreach (const d; [tuple("Alive", s.alive), tuple("Killed", s.killed), 63 tuple("Timeout", s.timeout), tuple("Total", s.total), tuple("Untested", 64 s.untested), tuple("Killed by compiler", s.killedByCompiler),]) { 65 tbl.appendRow(d[0], d[1]); 66 } 67 68 if (s.aliveNoMut != 0) { 69 tbl.appendRow("NoMut", s.aliveNoMut.to!string); 70 tbl.appendRow("NoMut/total", s.suppressedOfTotal.to!string); 71 72 auto p = n.addChild("p", "NoMut is the number of mutants that are alive but ignored."); 73 p.appendHtml(" They are <i>suppressed</i>."); 74 p.appendText(" This result in those mutants increasing the mutation score."); 75 p.appendText(" The suppressed/total is how much it has increased."); 76 p.appendHtml(" You <b>should</b> react if it is high."); 77 } 78 } 79 80 void deadTestCase(const TestCaseDeadStat s, Element n) { 81 if (s.numDeadTC == 0) 82 return; 83 84 n.addChild("h2", "Dead Test Cases"); 85 n.addChild("p", "These test case have killed zero mutants. There is a high probability that these contain implementation errors. They should be manually inspected."); 86 87 n.addChild("p", format("%s/%s = %s of all test cases", s.numDeadTC, s.total, s.ratio)); 88 89 auto tbl = tmplDefaultTable(n, ["Test Case"]); 90 foreach (tc; s.testCases) { 91 tbl.appendRow(tc.name); 92 } 93 } 94 95 void overlapTestCase(const TestCaseOverlapStat s, Element n) { 96 import std.algorithm : sort, map, filter; 97 import std.array : array; 98 import std.conv : to; 99 import std.range : enumerate; 100 101 if (s.total == 0) 102 return; 103 104 n.addChild("h2", "Overlapping Test Cases"); 105 n.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."); 106 107 n.addChild("p", s.sumToString); 108 109 auto tbl = tmplDefaultTable(n, ["Test Case", "Count", "Mutation IDs"]); 110 111 foreach (tcs; s.tc_mut.byKeyValue.filter!(a => a.value.length > 1).enumerate) { 112 bool first = true; 113 string cls = () { 114 if (tcs.index % 2 == 0) 115 return tableRowStyle; 116 return tableRowDarkStyle; 117 }(); 118 119 // TODO this is a bit slow. use a DB row iterator instead. 120 foreach (name; tcs.value.value.map!(id => s.name_tc[id].idup).array.sort) { 121 auto r = tbl.appendRow(); 122 if (first) { 123 r.addChild("td", name).addClass(cls); 124 r.addChild("td", s.mutid_mut[tcs.value.key].length.to!string).addClass(cls); 125 r.addChild("td", format("%(%s %)", s.mutid_mut[tcs.value.key])).addClass(cls); 126 } else { 127 r.addChild("td", name).addClass(cls); 128 } 129 first = false; 130 } 131 } 132 }