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.array : empty; 17 import std.exception : collectException; 18 import std.regex : matchFirst; 19 20 import dextool.type; 21 22 import dextool.plugin.mutate.type : MutationKind, AdminOperation; 23 import dextool.plugin.mutate.backend.database : Database, MutationStatusId; 24 import dextool.plugin.mutate.backend.type : Mutation, Offset, ExitStatus; 25 import dextool.plugin.mutate.backend.interface_ : FilesysIO; 26 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText; 27 28 auto makeAdmin() { 29 return BuildAdmin.init; 30 } 31 32 private: 33 34 import std.regex : regex, Regex; 35 36 struct BuildAdmin { 37 @safe: 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 MutationStatusId mutant_id; 47 string mutant_rationale; 48 FilesysIO fio; 49 AbsolutePath dbPath; 50 } 51 52 private InternalData data; 53 54 auto database(AbsolutePath v) nothrow { 55 data.dbPath = v; 56 return this; 57 } 58 59 auto operation(AdminOperation v) nothrow { 60 data.admin_op = v; 61 return this; 62 } 63 64 auto mutations(MutationKind[] v) nothrow { 65 import dextool.plugin.mutate.backend.mutation_type : toInternal; 66 67 return mutationsSubKind(toInternal(v)); 68 } 69 70 auto mutationsSubKind(Mutation.Kind[] v) nothrow { 71 if (!v.empty) { 72 data.kinds = v; 73 } 74 return this; 75 } 76 77 auto fromStatus(Mutation.Status v) nothrow { 78 data.status = v; 79 return this; 80 } 81 82 auto toStatus(Mutation.Status v) nothrow { 83 data.to_status = v; 84 return this; 85 } 86 87 auto testCaseRegex(string v) nothrow { 88 try { 89 data.test_case_regex = regex(v); 90 } catch (Exception e) { 91 logger.error(e.msg).collectException; 92 data.errorInData = true; 93 } 94 return this; 95 } 96 97 auto markMutantData(MutationStatusId v, string s, FilesysIO f) nothrow { 98 data.mutant_id = v; 99 data.mutant_rationale = s; 100 data.fio = f; 101 return this; 102 } 103 104 ExitStatusType run() @trusted { 105 if (data.errorInData) { 106 logger.error("Invalid parameters").collectException; 107 return ExitStatusType.Errors; 108 } 109 110 auto db = Database.make(data.dbPath); 111 return internalRun(db); 112 } 113 114 private ExitStatusType internalRun(ref Database db) { 115 final switch (data.admin_op) { 116 case AdminOperation.none: 117 logger.error("No admin operation specified").collectException; 118 return ExitStatusType.Errors; 119 case AdminOperation.resetMutant: 120 return resetMutant(db, data.kinds, 121 data.status, data.to_status); 122 case AdminOperation.removeMutant: 123 return removeMutant(db, data.kinds); 124 case AdminOperation.removeTestCase: 125 return removeTestCase(db, data.test_case_regex); 126 case AdminOperation.resetTestCase: 127 return resetTestCase(db, data.test_case_regex); 128 case AdminOperation.markMutant: 129 return markMutant(db, data.mutant_id, 130 data.kinds, data.to_status, data.mutant_rationale, data.fio); 131 case AdminOperation.removeMarkedMutant: 132 return removeMarkedMutant(db, data.mutant_id); 133 case AdminOperation.compact: 134 return compact(db); 135 case AdminOperation.stopTimeoutTest: 136 return stopTimeoutTest(db); 137 case AdminOperation.resetMutantSubKind: 138 return resetMutant(db, 139 data.kinds, data.status, data.to_status); 140 case AdminOperation.clearWorklist: 141 return clearWorklist(db); 142 } 143 } 144 } 145 146 ExitStatusType resetMutant(ref Database db, const Mutation.Kind[] kinds, 147 Mutation.Status status, Mutation.Status to_status) @safe nothrow { 148 try { 149 logger.infof("Resetting %s with status %s to %s", kinds, status, to_status); 150 db.mutantApi.resetMutant(kinds, status, to_status); 151 } catch (Exception e) { 152 logger.error(e.msg).collectException; 153 return ExitStatusType.Errors; 154 } 155 156 return ExitStatusType.Ok; 157 } 158 159 ExitStatusType removeMutant(ref Database db, const Mutation.Kind[] kinds) @safe nothrow { 160 try { 161 db.removeMutant(kinds); 162 } catch (Exception e) { 163 logger.error(e.msg).collectException; 164 return ExitStatusType.Errors; 165 } 166 167 return ExitStatusType.Ok; 168 } 169 170 ExitStatusType removeTestCase(ref Database db, const Regex!char re) @trusted nothrow { 171 import std.typecons : tuple; 172 173 try { 174 auto trans = db.transaction; 175 176 foreach (a; db.testCaseApi 177 .getDetectedTestCases 178 .filter!(a => !matchFirst(a.name, re).empty) 179 .map!(a => tuple!("tc", "id")(a, db.testCaseApi.getTestCaseId(a))) 180 .filter!(a => !a.id.isNull)) { 181 logger.info("Removing ", a.tc); 182 db.testCaseApi.removeTestCase(a.id.get); 183 } 184 185 trans.commit; 186 } catch (Exception e) { 187 logger.error(e.msg).collectException; 188 return ExitStatusType.Errors; 189 } 190 191 return ExitStatusType.Ok; 192 } 193 194 ExitStatusType resetTestCase(ref Database db, const Regex!char re) @trusted nothrow { 195 import std.typecons : tuple; 196 197 try { 198 auto trans = db.transaction; 199 200 foreach (a; db.testCaseApi 201 .getDetectedTestCases 202 .filter!(a => !matchFirst(a.name, re).empty) 203 .map!(a => tuple!("tc", "id")(a, db.testCaseApi.getTestCaseId(a))) 204 .filter!(a => !a.id.isNull)) { 205 logger.info("Resetting ", a.tc); 206 db.testCaseApi.resetTestCaseId(a.id.get); 207 } 208 209 trans.commit; 210 } catch (Exception e) { 211 logger.error(e.msg).collectException; 212 return ExitStatusType.Errors; 213 } 214 215 return ExitStatusType.Ok; 216 } 217 218 ExitStatusType markMutant(ref Database db, MutationStatusId id, const Mutation.Kind[] kinds, 219 const Mutation.Status status, string rationale, FilesysIO fio) @trusted nothrow { 220 import std.format : format; 221 import std.string : strip; 222 import dextool.plugin.mutate.backend.database : Rationale, toChecksum; 223 import dextool.plugin.mutate.backend.report.utility : window; 224 225 if (rationale.empty) { 226 logger.error("The rationale must be set").collectException; 227 return ExitStatusType.Errors; 228 } 229 230 try { 231 auto trans = db.transaction; 232 233 auto mut = db.mutantApi.getMutation(id); 234 if (mut.isNull) { 235 logger.errorf("Mutant with ID %s do not exist", id.get); 236 return ExitStatusType.Errors; 237 } 238 239 // because getMutation worked we know the ID is valid thus no need to 240 // check the return values when it or derived values are used. 241 242 const txt = () { 243 auto tmp = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)), 244 mut.get.mp.offset, db.mutantApi.getKind(id), mut.get.lang); 245 return window(format!"'%s'->'%s'"(tmp.original.strip, tmp.mutation.strip), 30); 246 }(); 247 248 db.markMutantApi.mark(mut.get.file, mut.get.sloc, id, toChecksum(id), 249 status, Rationale(rationale), txt); 250 251 db.mutantApi.update(id, status, ExitStatus(0)); 252 253 logger.infof(`Mutant %s marked with status %s and rationale %s`, id.get, status, rationale); 254 255 trans.commit; 256 return ExitStatusType.Ok; 257 } catch (Exception e) { 258 logger.trace(e).collectException; 259 logger.error(e.msg).collectException; 260 } 261 return ExitStatusType.Errors; 262 } 263 264 ExitStatusType removeMarkedMutant(ref Database db, MutationStatusId id) @trusted nothrow { 265 try { 266 auto trans = db.transaction; 267 268 // MutationStatusId used as check, removal of marking and updating status to unknown 269 if (db.markMutantApi.isMarked(id)) { 270 db.markMutantApi.remove(id); 271 db.mutantApi.update(id, Mutation.Status.unknown, ExitStatus(0)); 272 logger.infof("Removed marking for mutant %s.", id); 273 } else { 274 logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id.get); 275 } 276 277 trans.commit; 278 return ExitStatusType.Ok; 279 } catch (Exception e) { 280 logger.error(e.msg).collectException; 281 } 282 return ExitStatusType.Errors; 283 } 284 285 ExitStatusType compact(ref Database db) @trusted nothrow { 286 try { 287 logger.info("Running a SQL vacuum on the database"); 288 db.vacuum; 289 return ExitStatusType.Ok; 290 } catch (Exception e) { 291 logger.error(e.msg).collectException; 292 } 293 return ExitStatusType.Errors; 294 } 295 296 ExitStatusType stopTimeoutTest(ref Database db) @trusted nothrow { 297 import dextool.plugin.mutate.backend.database : Database, MutantTimeoutCtx; 298 import dextool.plugin.mutate.backend.test_mutant.timeout : MaxTimeoutIterations; 299 300 try { 301 logger.info("Forcing the testing of timeout mutants to stop"); 302 auto t = db.transaction; 303 304 db.timeoutApi.resetMutantTimeoutWorklist(Mutation.Status.timeout); 305 db.timeoutApi.clearMutantTimeoutWorklist; 306 db.worklistApi.clear; 307 308 MutantTimeoutCtx ctx; 309 ctx.iter = MaxTimeoutIterations; 310 ctx.state = MutantTimeoutCtx.State.done; 311 db.timeoutApi.put(ctx); 312 313 t.commit; 314 return ExitStatusType.Ok; 315 } catch (Exception e) { 316 logger.error(e.msg).collectException; 317 } 318 return ExitStatusType.Errors; 319 } 320 321 ExitStatusType clearWorklist(ref Database db) @trusted nothrow { 322 try { 323 logger.info("Clearing the mutant worklist"); 324 db.clearWorklist; 325 return ExitStatusType.Ok; 326 } catch (Exception e) { 327 logger.error(e.msg).collectException; 328 } 329 return ExitStatusType.Errors; 330 }