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 #SPC-report_for_human_plain 11 */ 12 module dextool.plugin.mutate.backend.report.plain; 13 14 import logger = std.experimental.logger; 15 import std.exception : collectException; 16 import std.typecons : Yes, No; 17 18 import dextool.type; 19 20 import dextool.plugin.mutate.backend.database : Database, IterateMutantRow, MutationId; 21 import dextool.plugin.mutate.backend.generate_mutant : MakeMutationTextResult, makeMutationText; 22 import dextool.plugin.mutate.backend.interface_ : FilesysIO; 23 import dextool.plugin.mutate.backend.report.analyzers : reportMutationSubtypeStats, reportMarkedMutants, 24 reportStatistics, MutationsMap, reportTestCaseKillMap, MutationReprMap, MutationRepr; 25 import dextool.plugin.mutate.backend.report.type : ReportEvent; 26 import dextool.plugin.mutate.backend.report.utility : window, windowSize, Table, toSections; 27 import dextool.plugin.mutate.backend.type : Mutation; 28 import dextool.plugin.mutate.config : ConfigReport; 29 import dextool.plugin.mutate.type : MutationKind, ReportKind, ReportLevel, ReportSection; 30 31 @safe: 32 33 /** Report mutations in a format easily readable by a human. 34 * 35 * TODO: use dextool.set for sections. 36 */ 37 @safe final class ReportPlain : ReportEvent { 38 import std.array : Appender; 39 import dextool.plugin.mutate.backend.utility; 40 41 const Mutation.Kind[] kinds; 42 const ConfigReport conf; 43 bool[ReportSection] sections; 44 FilesysIO fio; 45 46 long[MakeMutationTextResult] mutationStat; 47 48 MutationsMap testCaseMutationKilled; 49 MutationReprMap mutationReprMap; 50 Appender!(MutationId[]) testCaseSuggestions; 51 52 this(const Mutation.Kind[] kinds, const ConfigReport conf, FilesysIO fio) { 53 this.kinds = kinds; 54 this.fio = fio; 55 this.conf = conf; 56 57 ReportSection[] tmp_sec = conf.reportSection.length == 0 58 ? conf.reportLevel.toSections : conf.reportSection.dup; 59 60 foreach (a; tmp_sec) 61 this.sections[a] = true; 62 } 63 64 override void mutationKindEvent(const MutationKind[] kind_) { 65 } 66 67 override void locationStartEvent(ref Database db) @safe { 68 } 69 70 override void locationEvent(const ref IterateMutantRow r) @trusted { 71 void report() { 72 MakeMutationTextResult mut_txt; 73 AbsolutePath abs_path; 74 try { 75 abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir)); 76 mut_txt = makeMutationText(fio.makeInput(abs_path), 77 r.mutationPoint.offset, r.mutation.kind, r.lang); 78 } catch (Exception e) { 79 logger.warning(e.msg); 80 } 81 82 logger.infof("%s %s from '%s' to '%s' %s in %s:%s:%s", r.id, r.mutation.status, 83 mut_txt.original, mut_txt.mutation, r.attrs, abs_path, 84 r.sloc.line, r.sloc.column); 85 } 86 87 void updateMutationStat() { 88 if (r.mutation.status != Mutation.Status.alive) 89 return; 90 91 try { 92 auto abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir)); 93 auto mut_txt = makeMutationText(fio.makeInput(abs_path), 94 r.mutationPoint.offset, r.mutation.kind, r.lang); 95 96 if (auto v = mut_txt in mutationStat) 97 ++(*v); 98 else 99 mutationStat[mut_txt] = 1; 100 } catch (Exception e) { 101 logger.warning(e.msg); 102 } 103 } 104 105 void updateTestCaseStat() { 106 if (r.mutation.status != Mutation.Status.killed || r.testCases.length == 0) 107 return; 108 109 try { 110 auto abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir)); 111 auto mut_txt = makeMutationText(fio.makeInput(abs_path), 112 r.mutationPoint.offset, r.mutation.kind, r.lang); 113 } catch (Exception e) { 114 logger.warning(e.msg); 115 } 116 } 117 118 void updateTestCaseMap() { 119 if (r.mutation.status != Mutation.Status.killed || r.testCases.length == 0) 120 return; 121 122 try { 123 auto abs_path = AbsolutePath(FileName(r.file), DirName(fio.getOutputDir)); 124 auto mut_txt = makeMutationText(fio.makeInput(abs_path), 125 r.mutationPoint.offset, r.mutation.kind, r.lang); 126 mutationReprMap[r.id] = MutationRepr(r.sloc, r.file, mut_txt); 127 128 foreach (const a; r.testCases) { 129 testCaseMutationKilled[a][r.id] = true; 130 } 131 } catch (Exception e) { 132 logger.warning(e.msg); 133 } 134 } 135 136 void updateTestCaseSuggestion() { 137 if (r.mutation.status == Mutation.Status.alive) 138 testCaseSuggestions.put(r.id); 139 } 140 141 void reportTestCase() { 142 if (r.mutation.status != Mutation.Status.killed || r.testCases.length == 0) 143 return; 144 logger.infof("%s killed by [%(%s, %)]", r.id, r.testCases); 145 } 146 147 try { 148 if (ReportSection.alive in sections && r.mutation.status == Mutation.Status.alive) 149 report; 150 151 if (ReportSection.killed in sections && r.mutation.status == Mutation.Status.killed) 152 report; 153 154 if (ReportSection.all_mut in sections) 155 report; 156 157 if (ReportSection.mut_stat in sections) 158 updateMutationStat; 159 160 if (ReportSection.tc_killed in sections) 161 reportTestCase; 162 163 if (ReportSection.tc_stat in sections) 164 updateTestCaseStat(); 165 166 if (ReportSection.tc_map in sections) 167 updateTestCaseMap; 168 169 if (ReportSection.tc_suggestion in sections) 170 updateTestCaseSuggestion; 171 } catch (Exception e) { 172 logger.trace(e.msg).collectException; 173 } 174 } 175 176 override void locationEndEvent() { 177 } 178 179 override void locationStatEvent() { 180 import std.stdio : writeln; 181 182 if (ReportSection.tc_map in sections && testCaseMutationKilled.length != 0) { 183 logger.info("Test Case Kill Map"); 184 185 static void txtWriter(string s) { 186 writeln(s); 187 } 188 189 static void writer(ref Table!4 tbl) { 190 writeln(tbl); 191 } 192 193 reportTestCaseKillMap(testCaseMutationKilled, mutationReprMap, &txtWriter, &writer); 194 } 195 196 if (ReportSection.mut_stat in sections && mutationStat.length != 0) { 197 logger.info("Alive Mutation Statistics"); 198 199 Table!4 substat_tbl; 200 201 substat_tbl.heading = ["Percentage", "Count", "From", "To"]; 202 reportMutationSubtypeStats(mutationStat, substat_tbl); 203 204 writeln(substat_tbl); 205 } 206 } 207 208 override void statEvent(ref Database db) { 209 import std.stdio : stdout, File, writeln, writefln; 210 import dextool.plugin.mutate.backend.report.analyzers : reportTestCaseFullOverlap, 211 reportTestCaseStats, reportMutationTestCaseSuggestion, reportDeadTestCases, toTable; 212 213 auto stdout_ = () @trusted { return stdout; }(); 214 215 if (ReportSection.tc_stat in sections) { 216 logger.info("Test Case Kill Statistics"); 217 Table!3 tc_tbl; 218 219 tc_tbl.heading = ["Percentage", "Count", "TestCase"]; 220 const total = db.totalMutants(kinds); 221 reportTestCaseStats(db, kinds, conf.tcKillSortNum, conf.tcKillSortOrder, tc_tbl); 222 223 writeln(tc_tbl); 224 } 225 226 if (ReportSection.tc_killed_no_mutants in sections) { 227 logger.info("Test Case(s) that has killed no mutants"); 228 auto r = reportDeadTestCases(db); 229 if (r.ratio > 0) 230 writefln("%s/%s = %s of all test cases", r.numDeadTC, r.total, r.ratio); 231 232 Table!2 tbl; 233 tbl.heading = ["TestCase", "Location"]; 234 r.toTable(tbl); 235 writeln(tbl); 236 } 237 238 if (ReportSection.tc_suggestion in sections && testCaseSuggestions.data.length != 0) { 239 static void writer(ref Table!1 tbl) { 240 writeln(tbl); 241 } 242 243 reportMutationTestCaseSuggestion(db, testCaseSuggestions.data, &writer); 244 } 245 246 if (ReportSection.tc_full_overlap in sections 247 || ReportSection.tc_full_overlap_with_mutation_id in sections) { 248 auto stat = reportTestCaseFullOverlap(db, kinds); 249 250 if (ReportSection.tc_full_overlap in sections) { 251 logger.info("Redundant Test Cases (killing the same mutants)"); 252 Table!2 tbl; 253 stat.toTable!(No.colWithMutants)(tbl); 254 tbl.heading = ["TestCase", "Count"]; 255 writeln(stat.sumToString); 256 writeln(tbl); 257 } 258 259 if (ReportSection.tc_full_overlap_with_mutation_id in sections) { 260 logger.info("Redundant Test Cases (killing the same mutants)"); 261 Table!3 tbl; 262 stat.toTable!(Yes.colWithMutants)(tbl); 263 tbl.heading = ["TestCase", "Count", "Mutation ID"]; 264 writeln(stat.sumToString); 265 writeln(tbl); 266 } 267 } 268 269 if (ReportSection.marked_mutants in sections) { 270 logger.info("Marked mutants"); 271 auto r = reportMarkedMutants(db, kinds); 272 writeln(r.tbl); 273 } 274 275 if (ReportSection.summary in sections) { 276 logger.info("Summary"); 277 writeln(reportStatistics(db, kinds).toString); 278 } 279 280 writeln; 281 } 282 }