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