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