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