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