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