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