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