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 }