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.interface_ : FilesysIO; 22 import dextool.plugin.mutate.backend.type : Mutation; 23 import dextool.plugin.mutate.config : ConfigReport; 24 import dextool.plugin.mutate.type : MutationKind, ReportKind, ReportLevel, ReportSection; 25 26 import dextool.plugin.mutate.backend.report.utility : MakeMutationTextResult, 27 makeMutationText, Table, reportMutationSubtypeStats, 28 reportStatistics, MutationsMap, reportTestCaseKillMap, MutationReprMap, 29 MutationRepr, toSections; 30 import dextool.plugin.mutate.backend.report.type : ReportEvent; 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() { 69 } 70 71 override void locationEvent(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' in %s:%s:%s [%(%s, %)]", r.id, 84 r.mutation.status, mut_txt.original, mut_txt.mutation, 85 abs_path, r.sloc.line, r.sloc.column, r.attrs); 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() { 107 if (r.mutation.status != Mutation.Status.killed || r.testCases.length == 0) 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() { 120 if (r.mutation.status != Mutation.Status.killed || r.testCases.length == 0) 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; r.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() { 143 if (r.mutation.status != Mutation.Status.killed || r.testCases.length == 0) 144 return; 145 logger.infof("%s killed by [%(%s, %)]", r.id, r.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 if (ReportSection.tc_killed in sections) 162 reportTestCase; 163 164 if (ReportSection.tc_stat in sections) 165 updateTestCaseStat(); 166 167 if (ReportSection.tc_map in sections) 168 updateTestCaseMap; 169 170 if (ReportSection.tc_suggestion in sections) 171 updateTestCaseSuggestion; 172 } catch (Exception e) { 173 logger.trace(e.msg).collectException; 174 } 175 } 176 177 override void locationEndEvent() { 178 } 179 180 override void locationStatEvent() { 181 import std.stdio : writeln; 182 183 if (ReportSection.tc_map in sections && testCaseMutationKilled.length != 0) { 184 logger.info("Test Case Kill Map"); 185 186 static void txtWriter(string s) { 187 writeln(s); 188 } 189 190 static void writer(ref Table!4 tbl) { 191 writeln(tbl); 192 } 193 194 reportTestCaseKillMap(testCaseMutationKilled, mutationReprMap, &txtWriter, &writer); 195 } 196 197 if (ReportSection.mut_stat in sections && mutationStat.length != 0) { 198 logger.info("Alive Mutation Statistics"); 199 200 Table!4 substat_tbl; 201 202 substat_tbl.heading = ["Percentage", "Count", "From", "To"]; 203 reportMutationSubtypeStats(mutationStat, substat_tbl); 204 205 writeln(substat_tbl); 206 } 207 } 208 209 override void statEvent(ref Database db) { 210 import std.stdio : stdout, File, writeln, writefln; 211 import dextool.plugin.mutate.backend.report.utility : reportTestCaseFullOverlap, 212 reportTestCaseStats, reportMutationTestCaseSuggestion, reportDeadTestCases, toTable; 213 214 auto stdout_ = () @trusted { return stdout; }(); 215 216 if (ReportSection.tc_stat in sections) { 217 logger.info("Test Case Kill Statistics"); 218 Table!3 tc_tbl; 219 220 tc_tbl.heading = ["Percentage", "Count", "TestCase"]; 221 const total = db.totalMutants(kinds); 222 reportTestCaseStats(db, kinds, conf.tcKillSortNum, conf.tcKillSortOrder, tc_tbl); 223 224 writeln(tc_tbl); 225 } 226 227 if (ReportSection.tc_killed_no_mutants in sections) { 228 logger.info("Test Case(s) that has killed no mutants"); 229 auto r = reportDeadTestCases(db); 230 if (r.ratio > 0) 231 writefln("%s/%s = %s of all test cases", r.numDeadTC, r.total, r.ratio); 232 233 Table!2 tbl; 234 tbl.heading = ["TestCase", "Location"]; 235 r.toTable(tbl); 236 writeln(tbl); 237 } 238 239 if (ReportSection.tc_suggestion in sections && testCaseSuggestions.data.length != 0) { 240 static void writer(ref Table!1 tbl) { 241 writeln(tbl); 242 } 243 244 reportMutationTestCaseSuggestion(db, testCaseSuggestions.data, &writer); 245 } 246 247 if (ReportSection.tc_full_overlap in sections 248 || ReportSection.tc_full_overlap_with_mutation_id in sections) { 249 auto stat = reportTestCaseFullOverlap(db, kinds); 250 251 if (ReportSection.tc_full_overlap in sections) { 252 logger.info("Redundant Test Cases (killing the same mutants)"); 253 Table!2 tbl; 254 stat.toTable!(No.colWithMutants)(tbl); 255 tbl.heading = ["TestCase", "Count"]; 256 writeln(stat.sumToString); 257 writeln(tbl); 258 } 259 260 if (ReportSection.tc_full_overlap_with_mutation_id in sections) { 261 logger.info("Redundant Test Cases (killing the same mutants)"); 262 Table!3 tbl; 263 stat.toTable!(Yes.colWithMutants)(tbl); 264 tbl.heading = ["TestCase", "Count", "Mutation ID"]; 265 writeln(stat.sumToString); 266 writeln(tbl); 267 } 268 } 269 270 if (ReportSection.summary in sections) { 271 logger.info("Summary"); 272 writeln(reportStatistics(db, kinds).toString); 273 } 274 275 writeln; 276 } 277 }