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.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.getDetectedTestCases 177 .filter!(a => !matchFirst(a.name, re).empty) 178 .map!(a => tuple!("tc", "id")(a, db.getTestCaseId(a))) 179 .filter!(a => !a.id.isNull)) { 180 logger.info("Removing ", a.tc); 181 db.removeTestCase(a.id.get); 182 } 183 184 trans.commit; 185 } catch (Exception e) { 186 logger.error(e.msg).collectException; 187 return ExitStatusType.Errors; 188 } 189 190 return ExitStatusType.Ok; 191 } 192 193 ExitStatusType resetTestCase(ref Database db, const Regex!char re) @trusted nothrow { 194 import std.typecons : tuple; 195 196 try { 197 auto trans = db.transaction; 198 199 foreach (a; db.getDetectedTestCases 200 .filter!(a => !matchFirst(a.name, re).empty) 201 .map!(a => tuple!("tc", "id")(a, db.getTestCaseId(a))) 202 .filter!(a => !a.id.isNull)) { 203 logger.info("Resetting ", a.tc); 204 db.resetTestCaseId(a.id.get); 205 } 206 207 trans.commit; 208 } catch (Exception e) { 209 logger.error(e.msg).collectException; 210 return ExitStatusType.Errors; 211 } 212 213 return ExitStatusType.Ok; 214 } 215 216 ExitStatusType markMutant(ref Database db, MutationId id, const Mutation.Kind[] kinds, 217 const Mutation.Status status, string rationale, FilesysIO fio) @trusted nothrow { 218 import std.format : format; 219 import std..string : strip; 220 import dextool.plugin.mutate.backend.database : Rationale; 221 import dextool.plugin.mutate.backend.report.utility : window; 222 223 if (rationale.empty) { 224 logger.error("The rationale must be set").collectException; 225 return ExitStatusType.Errors; 226 } 227 228 try { 229 auto trans = db.transaction; 230 231 auto mut = db.getMutation(id); 232 if (mut.isNull) { 233 logger.errorf("Mutant with ID %s do not exist", id.get); 234 return ExitStatusType.Errors; 235 } 236 237 // because getMutation worked we know the ID is valid thus no need to 238 // check the return values when it or derived values are used. 239 240 const st_id = db.getMutationStatusId(id).get; 241 const checksum = db.getChecksum(st_id).get; 242 243 const txt = () { 244 auto tmp = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)), 245 mut.get.mp.offset, db.getKind(id), mut.get.lang); 246 return window(format!"'%s'->'%s'"(tmp.original.strip, tmp.mutation.strip), 30); 247 }(); 248 249 db.markMutant(id, mut.get.file, mut.get.sloc, st_id, checksum, status, 250 Rationale(rationale), txt); 251 252 db.updateMutationStatus(st_id, status, ExitStatus(0)); 253 254 logger.infof(`Mutant %s marked with status %s and rationale %s`, id.get, status, rationale); 255 256 trans.commit; 257 return ExitStatusType.Ok; 258 } catch (Exception e) { 259 logger.trace(e).collectException; 260 logger.error(e.msg).collectException; 261 } 262 return ExitStatusType.Errors; 263 } 264 265 ExitStatusType removeMarkedMutant(ref Database db, MutationId id) @trusted nothrow { 266 try { 267 auto trans = db.transaction; 268 269 // MutationStatusId used as check, removal of marking and updating status to unknown 270 const st_id = db.getMutationStatusId(id); 271 if (st_id.isNull) { 272 logger.errorf("Mutant with ID %s do not exist", id.get); 273 return ExitStatusType.Errors; 274 } 275 276 if (db.isMarked(id)) { 277 db.removeMarkedMutant(st_id.get); 278 db.updateMutationStatus(st_id.get, Mutation.Status.unknown, ExitStatus(0)); 279 logger.infof("Removed marking for mutant %s.", id); 280 } else { 281 logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id.get); 282 } 283 284 trans.commit; 285 return ExitStatusType.Ok; 286 } catch (Exception e) { 287 logger.error(e.msg).collectException; 288 } 289 return ExitStatusType.Errors; 290 } 291 292 ExitStatusType compact(ref Database db) @trusted nothrow { 293 try { 294 logger.info("Running a SQL vacuum on the database"); 295 db.vacuum; 296 return ExitStatusType.Ok; 297 } catch (Exception e) { 298 logger.error(e.msg).collectException; 299 } 300 return ExitStatusType.Errors; 301 } 302 303 ExitStatusType stopTimeoutTest(ref Database db) @trusted nothrow { 304 import dextool.plugin.mutate.backend.database : Database, MutantTimeoutCtx; 305 import dextool.plugin.mutate.backend.test_mutant.timeout : MaxTimeoutIterations; 306 307 try { 308 logger.info("Forcing the testing of timeout mutants to stop"); 309 auto t = db.transaction; 310 311 db.resetMutantTimeoutWorklist(Mutation.Status.timeout); 312 db.clearMutantTimeoutWorklist; 313 db.clearWorklist; 314 315 MutantTimeoutCtx ctx; 316 ctx.iter = MaxTimeoutIterations; 317 ctx.state = MutantTimeoutCtx.State.done; 318 db.putMutantTimeoutCtx(ctx); 319 320 t.commit; 321 return ExitStatusType.Ok; 322 } catch (Exception e) { 323 logger.error(e.msg).collectException; 324 } 325 return ExitStatusType.Errors; 326 } 327 328 ExitStatusType clearWorklist(ref Database db) @trusted nothrow { 329 try { 330 logger.info("Clearing the mutant worklist"); 331 db.clearWorklist; 332 return ExitStatusType.Ok; 333 } catch (Exception e) { 334 logger.error(e.msg).collectException; 335 } 336 return ExitStatusType.Errors; 337 }