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