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 module dextool.plugin.mutate.backend.report.json;
11 
12 import logger = std.experimental.logger;
13 import std.array : empty;
14 import std.exception : collectException;
15 import std.path : buildPath;
16 
17 import dextool.type;
18 
19 import dextool.plugin.mutate.backend.database : Database, FileRow, FileMutantRow, MutationId;
20 import dextool.plugin.mutate.backend.diff_parser : Diff;
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.type : FileReport, FilesReporter;
24 import dextool.plugin.mutate.backend.report.utility : window, windowSize, toSections;
25 import dextool.plugin.mutate.backend.type : Mutation;
26 import dextool.plugin.mutate.config : ConfigReport;
27 import dextool.plugin.mutate.type : MutationKind, ReportSection;
28 
29 @safe:
30 
31 /**
32  * Expects locations to be grouped by file.
33  *
34  * TODO this is ugly. Use a JSON serializer instead.
35  */
36 final class ReportJson : FileReport, FilesReporter {
37     import std.array : array;
38     import std.algorithm : map, joiner;
39     import std.conv : to;
40     import std.format : format;
41     import std.json;
42     import my.set;
43 
44     const Mutation.Kind[] kinds;
45     const AbsolutePath logDir;
46     Set!ReportSection sections;
47     FilesysIO fio;
48 
49     // Report alive mutants in this section
50     Diff diff;
51 
52     JSONValue report;
53     JSONValue[] current_file_mutants;
54     FileRow current_file;
55 
56     this(const Mutation.Kind[] kinds, const ConfigReport conf, FilesysIO fio, ref Diff diff) {
57         this.kinds = kinds;
58         this.fio = fio;
59         this.logDir = conf.logDir;
60         this.diff = diff;
61 
62         sections = (conf.reportSection.length == 0 ? conf.reportLevel.toSections
63                 : conf.reportSection.dup).toSet;
64     }
65 
66     override void mutationKindEvent(const MutationKind[] kinds) {
67         report = ["types": kinds.map!(a => a.to!string).array, "files": []];
68     }
69 
70     override FileReport getFileReportEvent(ref Database db, const ref FileRow fr) @trusted {
71         current_file = fr;
72         return this;
73     }
74 
75     override void fileMutantEvent(const ref FileMutantRow r) @trusted {
76         auto appendMutant() {
77             JSONValue m = ["id" : r.id.to!long];
78             m.object["kind"] = r.mutation.kind.to!string;
79             m.object["status"] = r.mutation.status.to!string;
80             m.object["line"] = r.sloc.line;
81             m.object["column"] = r.sloc.column;
82             m.object["begin"] = r.mutationPoint.offset.begin;
83             m.object["end"] = r.mutationPoint.offset.end;
84 
85             try {
86                 MakeMutationTextResult mut_txt;
87                 auto abs_path = AbsolutePath(buildPath(fio.getOutputDir, current_file.file.Path));
88                 mut_txt = makeMutationText(fio.makeInput(abs_path),
89                         r.mutationPoint.offset, r.mutation.kind, r.lang);
90                 m.object["value"] = mut_txt.mutation;
91             } catch (Exception e) {
92                 logger.warning(e.msg);
93             }
94 
95             current_file_mutants ~= m;
96         }
97 
98         if (sections.contains(ReportSection.all_mut) || sections.contains(ReportSection.alive)
99                 && r.mutation.status == Mutation.Status.alive
100                 || sections.contains(ReportSection.killed)
101                 && r.mutation.status == Mutation.Status.killed) {
102             appendMutant;
103         }
104     }
105 
106     override void endFileEvent(ref Database db) @trusted {
107         if (current_file_mutants.empty) {
108             return;
109         }
110 
111         JSONValue s;
112         s = [
113             "filename": current_file.file,
114             "checksum": format("%x", current_file.fileChecksum),
115         ];
116         s["mutations"] = JSONValue(current_file_mutants), report["files"].array ~= s;
117 
118         current_file_mutants = null;
119     }
120 
121     override void postProcessEvent(ref Database db) @trusted {
122         import std.datetime : Clock;
123         import std.path : buildPath;
124         import std.stdio : File;
125         import dextool.plugin.mutate.backend.report.analyzers : reportStatistics, reportDiff;
126         import dextool.plugin.mutate.backend.report.analyzers : DiffReport, reportDiff;
127 
128         if (ReportSection.summary in sections) {
129             const stat = reportStatistics(db, kinds);
130             JSONValue s = ["alive" : stat.alive];
131             s.object["aliveNoMut"] = stat.aliveNoMut;
132             s.object["killed"] = stat.killed;
133             s.object["timeout"] = stat.timeout;
134             s.object["untested"] = stat.untested;
135             s.object["killedByCompiler"] = stat.killedByCompiler;
136             s.object["total"] = stat.total;
137             s.object["score"] = stat.score;
138             s.object["nomutScore"] = stat.suppressedOfTotal;
139             s.object["totalTime"] = stat.totalTime.total!"seconds";
140             s.object["killedByCompilerTime"] = stat.killedByCompilerTime.total!"seconds";
141             s.object["predictedDone"] = (Clock.currTime + stat.predictedDone).toISOExtString;
142 
143             report["stat"] = s;
144         }
145 
146         if (ReportSection.diff in sections) {
147             auto r = reportDiff(db, kinds, diff, fio.getOutputDir);
148             JSONValue s = ["score" : r.score];
149             report["diff"] = s;
150         }
151 
152         File(buildPath(logDir, "report.json"), "w").write(report.toJSON(true));
153     }
154 
155     override void endEvent(ref Database) {
156     }
157 }