1 /**
2 Copyright: Copyright (c) 2017, 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 This module contains functionality for reporting the mutations. Both the
11 summary and details for each mutation.
12 */
13 module dextool.plugin.mutate.backend.report;
14 
15 import logger = std.experimental.logger;
16 import std.ascii : newline;
17 import std.exception : collectException;
18 
19 import dextool.type;
20 
21 import dextool.plugin.mutate.backend.database : Database, IterateMutantRow;
22 import dextool.plugin.mutate.backend.diff_parser : Diff, diffFromStdin;
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.type : SimpleWriter, ReportEvent,
26     FileReport, FilesReporter;
27 import dextool.plugin.mutate.backend.report.utility : window, windowSize;
28 import dextool.plugin.mutate.backend.type : Mutation, Offset;
29 import dextool.plugin.mutate.backend.utility : getProfileResult, Profile;
30 import dextool.plugin.mutate.config : ConfigReport;
31 import dextool.plugin.mutate.type : MutationKind, ReportKind, ReportLevel, ReportSection;
32 
33 ExitStatusType runReport(ref Database db, const MutationKind[] kind,
34         const ConfigReport conf, FilesysIO fio) @trusted nothrow {
35     Diff diff;
36     try {
37         if (conf.unifiedDiff)
38             diff = diffFromStdin;
39     } catch (Exception e) {
40         logger.warning(e.msg).collectException;
41     }
42 
43     try {
44         auto genrep = ReportGenerator.make(db, kind, conf, fio);
45         runAllMutantReporter(db, kind, genrep);
46 
47         auto fp = makeFilesReporter(db, conf, kind, fio, diff);
48         if (fp !is null)
49             runFilesReporter(db, fp, kind);
50     } catch (Exception e) {
51         debug logger.trace(e).collectException;
52         logger.error(e.msg).collectException;
53         return ExitStatusType.Errors;
54     }
55 
56     if (conf.profile)
57         try {
58             import std.stdio : writeln;
59 
60             writeln(getProfileResult.toString);
61         } catch (Exception e) {
62             logger.warning("Unable to print the profile data: ", e.msg).collectException;
63         }
64 
65     return ExitStatusType.Ok;
66 }
67 
68 @safe:
69 private:
70 
71 void runAllMutantReporter(ref Database db, const(MutationKind)[] kind, ref ReportGenerator genrep) {
72     import dextool.plugin.mutate.backend.utility;
73 
74     const auto kinds = dextool.plugin.mutate.backend.utility.toInternal(kind);
75 
76     // TODO remove this parameter. seems to be unnecessary.
77     genrep.mutationKindEvent(kind);
78 
79     genrep.locationStartEvent(db);
80     {
81         auto profile = Profile("iterate mutants for report");
82         db.iterateMutants(kinds, &genrep.locationEvent);
83     }
84 
85     auto profile = Profile("post process report");
86     genrep.locationEndEvent;
87     genrep.locationStatEvent;
88     genrep.statEvent(db);
89 }
90 
91 void runFilesReporter(ref Database db, FilesReporter fps, const(MutationKind)[] kind) {
92     assert(fps !is null, "report should never be null");
93 
94     import dextool.plugin.mutate.backend.utility;
95     import dextool.plugin.mutate.backend.database : FileMutantRow;
96 
97     const auto kinds = dextool.plugin.mutate.backend.utility.toInternal(kind);
98 
99     fps.mutationKindEvent(kind);
100 
101     foreach (f; db.getDetailedFiles) {
102         auto profile = Profile("generate report for " ~ f.file);
103         auto fp = fps.getFileReportEvent(db, f);
104         db.iterateFileMutants(kinds, f.file, &fp.fileMutantEvent);
105         fp.endFileEvent(db);
106     }
107 
108     auto profile = Profile("post process report");
109     fps.postProcessEvent(db);
110     fps.endEvent(db);
111 }
112 
113 FilesReporter makeFilesReporter(ref Database db, const ConfigReport conf,
114         const(MutationKind)[] kind, FilesysIO fio, ref Diff diff) {
115     import dextool.plugin.mutate.backend.report.html;
116     import dextool.plugin.mutate.backend.report.json;
117     import dextool.plugin.mutate.backend.utility;
118 
119     const auto kinds = dextool.plugin.mutate.backend.utility.toInternal(kind);
120 
121     final switch (conf.reportKind) {
122     case ReportKind.plain:
123     case ReportKind.markdown:
124     case ReportKind.compiler:
125     case ReportKind.csv:
126         return null;
127     case ReportKind.json:
128         return new ReportJson(kinds, conf, fio, diff);
129     case ReportKind.html:
130         return new ReportHtml(kinds, conf, fio, diff);
131     }
132 }
133 
134 /**
135  * Expects the event to come in the following order:
136  *  - mutationKindEvent
137  *  - locationStartEvent
138  *  - locationEvent
139  *  - locationEndEvent
140  *  - statStartEvent
141  *  - statEvent
142  *  - statEndEvent
143  */
144 struct ReportGenerator {
145     import std.algorithm : each;
146     import dextool.plugin.mutate.backend.report.compiler;
147     import dextool.plugin.mutate.backend.report.csv;
148     import dextool.plugin.mutate.backend.report.html;
149     import dextool.plugin.mutate.backend.report.markdown;
150     import dextool.plugin.mutate.backend.report.plain;
151 
152     Database db;
153     ReportEvent[] listeners;
154     FilesReporter fileReporter;
155 
156     static auto make(ref Database db, const MutationKind[] kind,
157             const ConfigReport conf, FilesysIO fio) @system {
158         import dextool.plugin.mutate.backend.utility;
159 
160         auto kinds = dextool.plugin.mutate.backend.utility.toInternal(kind);
161         ReportEvent[] listeners;
162 
163         final switch (conf.reportKind) {
164         case ReportKind.plain:
165             listeners = [new ReportPlain(kinds, conf, fio)];
166             break;
167         case ReportKind.markdown:
168             listeners = [new ReportMarkdown(kinds, conf, fio)];
169             break;
170         case ReportKind.compiler:
171             listeners = [new ReportCompiler(kinds, conf.reportLevel, fio)];
172             break;
173         case ReportKind.json:
174             listeners = null;
175             break;
176         case ReportKind.csv:
177             listeners = [new ReportCSV(kinds, conf.reportLevel, fio)];
178             break;
179         case ReportKind.html:
180             listeners = null;
181             break;
182         }
183 
184         return ReportGenerator(db, listeners);
185     }
186 
187     void mutationKindEvent(const MutationKind[] kind_) {
188         listeners.each!(a => a.mutationKindEvent(kind_));
189     }
190 
191     void locationStartEvent(ref Database db) {
192         listeners.each!(a => a.locationStartEvent(db));
193     }
194 
195     // trusted: trusting that d2sqlite3 and sqlite3 is memory safe.
196     void locationEvent(const ref IterateMutantRow r) @trusted {
197         listeners.each!(a => a.locationEvent(db, r));
198     }
199 
200     void locationEndEvent() {
201         listeners.each!(a => a.locationEndEvent);
202     }
203 
204     void locationStatEvent() {
205         listeners.each!(a => a.locationStatEvent);
206     }
207 
208     void statEvent(ref Database db) {
209         listeners.each!(a => a.statEvent(db));
210     }
211 }