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 }