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