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 This module contains functionality to administrate the database 11 */ 12 module dextool.plugin.mutate.backend.admin; 13 14 import logger = std.experimental.logger; 15 import std.algorithm : filter, map; 16 import std.exception : collectException; 17 import std.regex : matchFirst; 18 19 import dextool.type; 20 21 import dextool.plugin.mutate.type : MutationKind, AdminOperation; 22 import dextool.plugin.mutate.backend.database : Database, MutationId; 23 import dextool.plugin.mutate.backend.type : Mutation, Offset; 24 import dextool.plugin.mutate.backend.interface_ : FilesysIO; 25 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText; 26 27 auto makeAdmin() { 28 return BuildAdmin.init; 29 } 30 31 private: 32 33 import std.regex : regex, Regex; 34 35 struct BuildAdmin { 36 @safe: 37 nothrow: 38 private struct InternalData { 39 bool errorInData; 40 41 AdminOperation admin_op; 42 Mutation.Kind[] kinds; 43 Mutation.Status status; 44 Mutation.Status to_status; 45 Regex!char test_case_regex; 46 MutationId mutant_id; 47 string mutant_rationale; 48 FilesysIO fio; 49 } 50 51 private InternalData data; 52 53 auto operation(AdminOperation v) { 54 data.admin_op = v; 55 return this; 56 } 57 58 auto mutations(MutationKind[] v) { 59 import dextool.plugin.mutate.backend.utility; 60 61 data.kinds = toInternal(v); 62 return this; 63 } 64 65 auto fromStatus(Mutation.Status v) { 66 data.status = v; 67 return this; 68 } 69 70 auto toStatus(Mutation.Status v) { 71 data.to_status = v; 72 return this; 73 } 74 75 auto testCaseRegex(string v) { 76 try { 77 data.test_case_regex = regex(v); 78 } catch (Exception e) { 79 logger.error(e.msg).collectException; 80 data.errorInData = true; 81 } 82 return this; 83 } 84 85 auto markMutantData(long v, string s, FilesysIO f) { 86 data.mutant_id = v; 87 data.mutant_rationale = s; 88 data.fio = f; 89 return this; 90 } 91 92 ExitStatusType run(ref Database db) { 93 if (data.errorInData) { 94 logger.error("Invalid parameters").collectException; 95 return ExitStatusType.Errors; 96 } 97 98 final switch (data.admin_op) { 99 case AdminOperation.none: 100 logger.error("No admin operation specified").collectException; 101 return ExitStatusType.Errors; 102 case AdminOperation.resetMutant: 103 return resetMutant(db, data.kinds, 104 data.status, data.to_status); 105 case AdminOperation.removeMutant: 106 return removeMutant(db, data.kinds); 107 case AdminOperation.removeTestCase: 108 return removeTestCase(db, data.test_case_regex); 109 case AdminOperation.resetTestCase: 110 return resetTestCase(db, data.test_case_regex); 111 case AdminOperation.markMutant: 112 return markMutant(db, data.mutant_id, 113 data.kinds, data.to_status, data.mutant_rationale, data.fio); 114 case AdminOperation.removeMarkedMutant: 115 return removeMarkedMutant(db, data.mutant_id); 116 case AdminOperation.compact: 117 return compact(db); 118 } 119 } 120 } 121 122 ExitStatusType resetMutant(ref Database db, const Mutation.Kind[] kinds, 123 Mutation.Status status, Mutation.Status to_status) @safe nothrow { 124 try { 125 db.resetMutant(kinds, status, to_status); 126 } catch (Exception e) { 127 logger.error(e.msg).collectException; 128 return ExitStatusType.Errors; 129 } 130 131 return ExitStatusType.Ok; 132 } 133 134 ExitStatusType removeMutant(ref Database db, const Mutation.Kind[] kinds) @safe nothrow { 135 try { 136 db.removeMutant(kinds); 137 } catch (Exception e) { 138 logger.error(e.msg).collectException; 139 return ExitStatusType.Errors; 140 } 141 142 return ExitStatusType.Ok; 143 } 144 145 ExitStatusType removeTestCase(ref Database db, const Regex!char re) @trusted nothrow { 146 import std.typecons : tuple; 147 148 try { 149 auto trans = db.transaction; 150 151 foreach (a; db.getDetectedTestCases 152 .filter!(a => !matchFirst(a.name, re).empty) 153 .map!(a => tuple!("tc", "id")(a, db.getTestCaseId(a))) 154 .filter!(a => !a.id.isNull)) { 155 logger.info("Removing ", a.tc); 156 db.removeTestCase(a.id.get); 157 } 158 159 trans.commit; 160 } catch (Exception e) { 161 logger.error(e.msg).collectException; 162 return ExitStatusType.Errors; 163 } 164 165 return ExitStatusType.Ok; 166 } 167 168 ExitStatusType resetTestCase(ref Database db, const Regex!char re) @trusted nothrow { 169 import std.typecons : tuple; 170 171 try { 172 auto trans = db.transaction; 173 174 foreach (a; db.getDetectedTestCases 175 .filter!(a => !matchFirst(a.name, re).empty) 176 .map!(a => tuple!("tc", "id")(a, db.getTestCaseId(a))) 177 .filter!(a => !a.id.isNull)) { 178 logger.info("Resetting ", a.tc); 179 db.resetTestCaseId(a.id.get); 180 } 181 182 trans.commit; 183 } catch (Exception e) { 184 logger.error(e.msg).collectException; 185 return ExitStatusType.Errors; 186 } 187 188 return ExitStatusType.Ok; 189 } 190 191 ExitStatusType markMutant(ref Database db, MutationId id, const Mutation.Kind[] kinds, 192 const Mutation.Status status, string rationale, FilesysIO fio) @trusted nothrow { 193 import dextool.plugin.mutate.backend.database : Rationale; 194 195 try { 196 auto trans = db.transaction; 197 198 auto mut = db.getMutation(id); 199 if (mut.isNull) { 200 logger.errorf("Failure when marking mutant: %s", id); 201 return ExitStatusType.Errors; 202 } 203 204 // because getMutation worked we know the ID is valid thus no need to 205 // check the return values when it or derived values are used. 206 207 const st_id = db.getMutationStatusId(id).get; 208 const checksum = db.getChecksum(st_id).get; 209 210 // assume that mutant has kind 211 const txt = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)), 212 mut.get.mp.offset, db.getKind(id), mut.get.lang).mutation; 213 214 db.markMutant(id, mut.get.file, mut.get.sloc, st_id, checksum, status, 215 Rationale(rationale), txt.idup); 216 217 db.updateMutationStatus(st_id, status); 218 219 logger.infof(`Mutant %s marked with status %s and rationale %s`, id, status, rationale); 220 221 trans.commit; 222 return ExitStatusType.Ok; 223 } catch (Exception e) { 224 logger.trace(e).collectException; 225 logger.error(e.msg).collectException; 226 } 227 return ExitStatusType.Errors; 228 } 229 230 ExitStatusType removeMarkedMutant(ref Database db, MutationId id) @trusted nothrow { 231 try { 232 auto trans = db.transaction; 233 234 // MutationStatusId used as check, removal of marking and updating status to unknown 235 const st_id = db.getMutationStatusId(id); 236 if (st_id.isNull) { 237 logger.errorf("Failure when removing marked mutant: %s", id); 238 return ExitStatusType.Errors; 239 } 240 241 if (db.isMarked(id)) { 242 db.removeMarkedMutant(st_id.get); 243 db.updateMutationStatus(st_id.get, Mutation.Status.unknown); 244 logger.infof("Removed marking for mutant %s.", id); 245 } else { 246 logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id); 247 } 248 249 trans.commit; 250 return ExitStatusType.Ok; 251 } catch (Exception e) { 252 logger.error(e.msg).collectException; 253 } 254 return ExitStatusType.Errors; 255 } 256 257 ExitStatusType compact(ref Database db) @trusted nothrow { 258 try { 259 logger.info("Running a SQL vacuum on the database"); 260 db.run("VACUUM"); 261 return ExitStatusType.Ok; 262 } catch (Exception e) { 263 logger.error(e.msg).collectException; 264 } 265 return ExitStatusType.Errors; 266 }