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 }