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, MutationId; 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 MutationId 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(MutationId 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, MutationId 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; 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 st_id = db.mutantApi.getMutationStatusId(id).get; 243 const checksum = db.mutantApi.getChecksum(st_id).get; 244 245 const txt = () { 246 auto tmp = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)), 247 mut.get.mp.offset, db.mutantApi.getKind(id), mut.get.lang); 248 return window(format!"'%s'->'%s'"(tmp.original.strip, tmp.mutation.strip), 30); 249 }(); 250 251 db.markMutantApi.markMutant(id, mut.get.file, mut.get.sloc, st_id, 252 checksum, status, Rationale(rationale), txt); 253 254 db.mutantApi.updateMutationStatus(st_id, status, ExitStatus(0)); 255 256 logger.infof(`Mutant %s marked with status %s and rationale %s`, id.get, status, rationale); 257 258 trans.commit; 259 return ExitStatusType.Ok; 260 } catch (Exception e) { 261 logger.trace(e).collectException; 262 logger.error(e.msg).collectException; 263 } 264 return ExitStatusType.Errors; 265 } 266 267 ExitStatusType removeMarkedMutant(ref Database db, MutationId id) @trusted nothrow { 268 try { 269 auto trans = db.transaction; 270 271 // MutationStatusId used as check, removal of marking and updating status to unknown 272 const st_id = db.mutantApi.getMutationStatusId(id); 273 if (st_id.isNull) { 274 logger.errorf("Mutant with ID %s do not exist", id.get); 275 return ExitStatusType.Errors; 276 } 277 278 if (db.markMutantApi.isMarked(id)) { 279 db.markMutantApi.removeMarkedMutant(st_id.get); 280 db.mutantApi.updateMutationStatus(st_id.get, Mutation.Status.unknown, ExitStatus(0)); 281 logger.infof("Removed marking for mutant %s.", id); 282 } else { 283 logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id.get); 284 } 285 286 trans.commit; 287 return ExitStatusType.Ok; 288 } catch (Exception e) { 289 logger.error(e.msg).collectException; 290 } 291 return ExitStatusType.Errors; 292 } 293 294 ExitStatusType compact(ref Database db) @trusted nothrow { 295 try { 296 logger.info("Running a SQL vacuum on the database"); 297 db.vacuum; 298 return ExitStatusType.Ok; 299 } catch (Exception e) { 300 logger.error(e.msg).collectException; 301 } 302 return ExitStatusType.Errors; 303 } 304 305 ExitStatusType stopTimeoutTest(ref Database db) @trusted nothrow { 306 import dextool.plugin.mutate.backend.database : Database, MutantTimeoutCtx; 307 import dextool.plugin.mutate.backend.test_mutant.timeout : MaxTimeoutIterations; 308 309 try { 310 logger.info("Forcing the testing of timeout mutants to stop"); 311 auto t = db.transaction; 312 313 db.timeoutApi.resetMutantTimeoutWorklist(Mutation.Status.timeout); 314 db.timeoutApi.clearMutantTimeoutWorklist; 315 db.worklistApi.clearWorklist; 316 317 MutantTimeoutCtx ctx; 318 ctx.iter = MaxTimeoutIterations; 319 ctx.state = MutantTimeoutCtx.State.done; 320 db.timeoutApi.putMutantTimeoutCtx(ctx); 321 322 t.commit; 323 return ExitStatusType.Ok; 324 } catch (Exception e) { 325 logger.error(e.msg).collectException; 326 } 327 return ExitStatusType.Errors; 328 } 329 330 ExitStatusType clearWorklist(ref Database db) @trusted nothrow { 331 try { 332 logger.info("Clearing the mutant worklist"); 333 db.clearWorklist; 334 return ExitStatusType.Ok; 335 } catch (Exception e) { 336 logger.error(e.msg).collectException; 337 } 338 return ExitStatusType.Errors; 339 }