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 }