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