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