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.noCoverage: "The mutant is never executed by the test suite.", 78 MutantStatus.equivalent 79 : "No change in the test case binaries happens when the mutant is injected and compiled.", 80 MutantStatus.skipped: "The mutant is skipped because another mutant that covers it survived (is alive).", 81 MutantStatus.nomut 82 : "The mutant is manually marked as not interesting. There is no intention of writing a test to kill it." 83 ]; 84 85 statusColor = cast(immutable)[ 86 //Mutation.Status.unknown:, 87 // light red 88 MutantStatus.alive: "background-color: #ff9980", 89 // light green 90 MutantStatus.killed: "background-color: #b3ff99", 91 MutantStatus.killedByCompiler: "background-color: #b3ff99", 92 MutantStatus.timeout: "background-color: #b3ff99", 93 MutantStatus.noCoverage: "background-color: #ff9980", 94 //Mutation.Status.equivalent:, 95 //Mutation.Status.skipped:, 96 ]; 97 } 98 99 void makeAllMutantsPage(ref Database db, const(Mutation.Kind)[] kinds, const AbsolutePath pageFname) @system { 100 auto doc = tmplBasicPage.dashboardCss; 101 scope (success) 102 () { 103 import std.stdio : File; 104 105 auto fout = File(pageFname, "w"); 106 fout.write(doc.toPrettyString); 107 }(); 108 109 doc.title(format("Mutants %s", Clock.currTime)); 110 doc.mainBody.setAttribute("onload", "init()"); 111 112 { 113 auto data = dashboard(); 114 auto style = doc.root.childElements("head")[0].addChild("style"); 115 style.addChild(new RawSource(doc, data.bootstrapCss.get)); 116 style.addChild(new RawSource(doc, data.dashboardCss.get)); 117 style.addChild(new RawSource(doc, tmplDefaultCss)); 118 119 auto script = doc.root.childElements("head")[0].addChild("script"); 120 script.addChild(new RawSource(doc, jsIndex)); 121 } 122 123 auto root = doc.mainBody; 124 root.addChild("p", 125 "Priority: how important it is to kill the mutant. It is based on modified source code size."); 126 root.addChild("p", 127 "ExitCode: the exit code of the test suite when the mutant where killed. 1: normal"); 128 root.addChild("p", 129 "Tests: number of tests that killed the mutant (failed when it was executed)."); 130 root.addChild("p", "Tested: date when the mutant was last tested/executed."); 131 132 const tabGroupName = "mutant_status"; 133 Element[MutantStatus] tabLink; 134 135 { // tab links 136 auto tab = root.addChild("div").addClass("tab"); 137 foreach (const status; [EnumMembers!MutantStatus]) { 138 auto b = tab.addChild("button").addClass("tablinks") 139 .addClass("tablinks_" ~ tabGroupName); 140 b.setAttribute("onclick", format!`openTab(event, '%s', '%s')`(status, tabGroupName)); 141 b.appendText(status.to!string); 142 tabLink[status] = b; 143 } 144 } 145 146 Table[MutantStatus] tabContent; 147 foreach (const status; [EnumMembers!MutantStatus]) { 148 auto div = root.addChild("div").addClass("tabcontent") 149 .addClass("tabcontent_" ~ tabGroupName).setAttribute("id", status.to!string); 150 div.addChild("p", statusDescription[status]); 151 tabContent[status] = tmplSortableTable(div, [ 152 "Link", "Priority", "ExitCode", "Tests", "Tested" 153 ]); 154 } 155 156 long[MutantStatus] statusCnt; 157 addMutants(db, kinds, tabContent, statusCnt); 158 159 foreach (a; statusCnt.byKeyValue) { 160 tabLink[a.key].appendText(format!" %s"(a.value)); 161 if (auto c = a.key in statusColor) 162 tabLink[a.key].style = *c; 163 } 164 } 165 166 void addMutants(ref Database db, const(Mutation.Kind)[] kinds, 167 ref Table[MutantStatus] content, ref long[MutantStatus] statusCnt) @system { 168 import std.path : buildPath; 169 import dextool.plugin.mutate.backend.database : IterateMutantRow2, MutationId; 170 171 static string toLinkPath(Path path, MutationId id) { 172 return format!"%s#%s"(buildPath(HtmlStyle.fileDir, pathToHtmlLink(path)), id); 173 } 174 175 void mutant(ref const IterateMutantRow2 mut) { 176 const status = () { 177 if (mut.attrs.isNoMut) 178 return MutantStatus.nomut; 179 return toStatus(mut.mutant.status); 180 }(); 181 182 statusCnt[status] += 1; 183 auto r = content[status].appendRow; 184 185 r.addChild("td").addChild("a", format("%s:%s", mut.file, 186 mut.sloc.line)).href = toLinkPath(mut.file, mut.id); 187 r.addChild("td", mut.prio.get.to!string); 188 r.addChild("td", toString(mut.exitStatus)); 189 r.addChild("td", mut.killedByTestCases.to!string); 190 r.addChild("td", mut.mutant.status == Mutation.Status.unknown ? "" : mut.tested.toShortDate); 191 } 192 193 db.iterateMutants(kinds, &mutant); 194 } 195 196 void makeHighInterestMutants(ref Database db, const(Mutation.Kind)[] kinds, 197 typeof(ConfigReport.highInterestMutantsNr) showInterestingMutants, Element root) @trusted { 198 import std.path : buildPath; 199 import dextool.plugin.mutate.backend.report.html.utility : pathToHtmlLink; 200 201 const sample = reportSelectedAliveMutants(db, kinds, showInterestingMutants.get); 202 if (sample.highestPrio.empty) 203 return; 204 205 DashboardCss.h3(root, "High Interest Mutants"); 206 207 if (sample.highestPrio.length != 0) { 208 root.addChild("p", format("This list the %s mutants that affect the most source code and has survived.", 209 sample.highestPrio.length)); 210 auto tbl_container = root.addChild("div").addClass("tbl_container"); 211 auto tbl = tmplDefaultTable(tbl_container, [ 212 "Link", "Tested", "Priority" 213 ]); 214 215 foreach (const mutst; sample.highestPrio) { 216 const mut = sample.mutants[mutst.statusId]; 217 auto r = tbl.appendRow(); 218 r.addChild("td").addChild("a", format("%s:%s", mut.file, 219 mut.sloc.line)).href = format("%s#%s", buildPath(HtmlStyle.fileDir, 220 pathToHtmlLink(mut.file)), mut.id.get); 221 r.addChild("td", mutst.updated.toString); 222 r.addChild("td", mutst.prio.get.to!string); 223 } 224 } 225 }