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