1 /** 2 Copyright: Copyright (c) 2021, 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_mutant; 11 12 import logger = std.experimental.logger; 13 import std.array : empty; 14 import std.conv : to; 15 import std.datetime : Clock; 16 import std.format : format; 17 import std.traits : EnumMembers; 18 19 import arsd.dom : Document, Element, require, Table, RawSource, Link; 20 import my.path : AbsolutePath, Path; 21 22 import dextool.cachetools; 23 import dextool.plugin.mutate.backend.database : Database; 24 import dextool.plugin.mutate.backend.report.analyzers : reportSelectedAliveMutants; 25 import dextool.plugin.mutate.backend.report.html.constants : HtmlStyle = Html, DashboardCss; 26 import dextool.plugin.mutate.backend.report.html.constants; 27 import dextool.plugin.mutate.backend.report.html.tmpl : tmplBasicPage, 28 tmplDefaultTable, dashboardCss, tmplDefaultMatrixTable, tmplSortableTable; 29 import dextool.plugin.mutate.backend.report.html.utility : pathToHtmlLink, toShortDate; 30 import dextool.plugin.mutate.backend.resource; 31 import dextool.plugin.mutate.backend.type : Mutation, toString; 32 import dextool.plugin.mutate.config : ConfigReport; 33 import dextool.plugin.mutate.type : MutationKind; 34 35 @safe: 36 37 void makeMutantPage(ref Database db, string tag, Element root, ref const ConfigReport conf, 38 const(Mutation.Kind)[] kinds, const AbsolutePath mutantPageFname) @trusted { 39 DashboardCss.h2(root.addChild(new Link(tag, null)).setAttribute("id", tag[1 .. $]), "Mutants"); 40 41 root.addChild("a", "All mutants").href = mutantPageFname.baseName; 42 43 makeAllMutantsPage(db, kinds, mutantPageFname); 44 makeHighInterestMutants(db, kinds, conf.highInterestMutantsNr, root); 45 } 46 47 private: 48 49 string mixinMutantStatus() { 50 string s; 51 s ~= "enum MutantStatus {"; 52 foreach (a; [EnumMembers!(Mutation.Status)]) 53 s ~= a.to!string ~ ","; 54 s ~= "nomut"; 55 s ~= "}"; 56 return s; 57 } 58 59 mixin(mixinMutantStatus); 60 61 MutantStatus toStatus(Mutation.Status s) { 62 return cast(MutantStatus) s; 63 } 64 65 immutable string[MutantStatus] statusDescription; 66 immutable string[MutantStatus] statusColor; 67 68 shared static this() @trusted { 69 statusDescription = cast(immutable)[ 70 MutantStatus.unknown: "Mutants that haven't been tested yet.", 71 MutantStatus.alive: "No test case failed when the mutant is tested.", 72 MutantStatus.killed: "At least one test case fail when the mutant is tested.", 73 MutantStatus.killedByCompiler 74 : "The compiler found and killed the mutant.", 75 MutantStatus.timeout 76 : "The test suite never terminate, infinite loop, when the mutant is tested.", 77 MutantStatus.memOverload 78 : "The test suite where terminated because the system memory limit triggered.", 79 MutantStatus.noCoverage: "The mutant is never executed by the test suite.", 80 MutantStatus.equivalent 81 : "No change in the test case binaries happens when the mutant is injected and compiled.", 82 MutantStatus.skipped: "The mutant is skipped because another mutant that covers it survived (is alive).", 83 MutantStatus.nomut 84 : "The mutant is manually marked as not interesting. There is no intention of writing a test to kill it." 85 ]; 86 87 statusColor = cast(immutable)[ 88 //Mutation.Status.unknown:, 89 // light red 90 MutantStatus.alive: "background-color: #ff9980", 91 // light green 92 MutantStatus.killed: "background-color: #b3ff99", 93 MutantStatus.killedByCompiler: "background-color: #b3ff99", 94 MutantStatus.timeout: "background-color: #b3ff99", 95 MutantStatus.memOverload: "background-color: #b3ff99", 96 MutantStatus.noCoverage: "background-color: #ff9980", 97 //Mutation.Status.equivalent:, 98 //Mutation.Status.skipped:, 99 ]; 100 } 101 102 void makeAllMutantsPage(ref Database db, const(Mutation.Kind)[] kinds, const AbsolutePath pageFname) @system { 103 auto doc = tmplBasicPage.dashboardCss; 104 scope (success) 105 () { 106 import std.stdio : File; 107 108 auto fout = File(pageFname, "w"); 109 fout.write(doc.toPrettyString); 110 }(); 111 112 doc.title(format("Mutants %s", Clock.currTime)); 113 doc.mainBody.setAttribute("onload", "init()"); 114 115 { 116 auto data = dashboard(); 117 auto style = doc.root.childElements("head")[0].addChild("style"); 118 style.addChild(new RawSource(doc, data.bootstrapCss.get)); 119 style.addChild(new RawSource(doc, data.dashboardCss.get)); 120 style.addChild(new RawSource(doc, tmplDefaultCss)); 121 122 auto script = doc.root.childElements("head")[0].addChild("script"); 123 script.addChild(new RawSource(doc, jsIndex)); 124 } 125 126 auto root = doc.mainBody; 127 root.addChild("p", 128 "Priority: how important it is to kill the mutant. It is based on modified source code size."); 129 root.addChild("p", 130 "ExitCode: the exit code of the test suite when the mutant where killed. 1: normal"); 131 root.addChild("p", 132 "Tests: number of tests that killed the mutant (failed when it was executed)."); 133 root.addChild("p", "Tested: date when the mutant was last tested/executed."); 134 135 const tabGroupName = "mutant_status"; 136 Element[MutantStatus] tabLink; 137 138 { // tab links 139 auto tab = root.addChild("div").addClass("tab"); 140 foreach (const status; [EnumMembers!MutantStatus]) { 141 auto b = tab.addChild("button").addClass("tablinks") 142 .addClass("tablinks_" ~ tabGroupName); 143 b.setAttribute("onclick", format!`openTab(event, '%s', '%s')`(status, tabGroupName)); 144 b.appendText(status.to!string); 145 tabLink[status] = b; 146 } 147 } 148 149 Table[MutantStatus] tabContent; 150 foreach (const status; [EnumMembers!MutantStatus]) { 151 auto div = root.addChild("div").addClass("tabcontent") 152 .addClass("tabcontent_" ~ tabGroupName).setAttribute("id", status.to!string); 153 div.addChild("p", statusDescription[status]); 154 tabContent[status] = tmplSortableTable(div, [ 155 "Link", "Priority", "ExitCode", "Tests", "Tested" 156 ]); 157 } 158 159 long[MutantStatus] statusCnt; 160 addMutants(db, kinds, tabContent, statusCnt); 161 162 foreach (a; statusCnt.byKeyValue) { 163 tabLink[a.key].appendText(format!" %s"(a.value)); 164 if (auto c = a.key in statusColor) 165 tabLink[a.key].style = *c; 166 } 167 } 168 169 void addMutants(ref Database db, const(Mutation.Kind)[] kinds, 170 ref Table[MutantStatus] content, ref long[MutantStatus] statusCnt) @system { 171 import std.path : buildPath; 172 import dextool.plugin.mutate.backend.database : IterateMutantRow2, MutationId; 173 174 static string toLinkPath(Path path, MutationId id) { 175 return format!"%s#%s"(buildPath(HtmlStyle.fileDir, pathToHtmlLink(path)), id); 176 } 177 178 void mutant(ref const IterateMutantRow2 mut) { 179 const status = () { 180 if (mut.attrs.isNoMut) 181 return MutantStatus.nomut; 182 return toStatus(mut.mutant.status); 183 }(); 184 185 statusCnt[status] += 1; 186 auto r = content[status].appendRow; 187 188 r.addChild("td").addChild("a", format("%s:%s", mut.file, 189 mut.sloc.line)).href = toLinkPath(mut.file, mut.id); 190 r.addChild("td", mut.prio.get.to!string); 191 r.addChild("td", toString(mut.exitStatus)); 192 r.addChild("td", mut.killedByTestCases.to!string); 193 r.addChild("td", mut.mutant.status == Mutation.Status.unknown ? "" : mut.tested.toShortDate); 194 } 195 196 db.iterateMutants(kinds, &mutant); 197 } 198 199 void makeHighInterestMutants(ref Database db, const(Mutation.Kind)[] kinds, 200 typeof(ConfigReport.highInterestMutantsNr) showInterestingMutants, Element root) @trusted { 201 import std.path : buildPath; 202 import dextool.plugin.mutate.backend.report.html.utility : pathToHtmlLink; 203 204 const sample = reportSelectedAliveMutants(db, kinds, showInterestingMutants.get); 205 if (sample.highestPrio.empty) 206 return; 207 208 DashboardCss.h3(root, "High Interest Mutants"); 209 210 if (sample.highestPrio.length != 0) { 211 root.addChild("p", format("This list the %s mutants that affect the most source code and has survived.", 212 sample.highestPrio.length)); 213 auto tbl_container = root.addChild("div").addClass("tbl_container"); 214 auto tbl = tmplDefaultTable(tbl_container, [ 215 "Link", "Tested", "Priority" 216 ]); 217 218 foreach (const mutst; sample.highestPrio) { 219 const mut = sample.mutants[mutst.statusId]; 220 auto r = tbl.appendRow(); 221 r.addChild("td").addChild("a", format("%s:%s", mut.file, 222 mut.sloc.line)).href = format("%s#%s", buildPath(HtmlStyle.fileDir, 223 pathToHtmlLink(mut.file)), mut.id.get); 224 r.addChild("td", mutst.updated.toString); 225 r.addChild("td", mutst.prio.get.to!string); 226 } 227 } 228 }