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 }