1 /** 2 Copyright: Copyright (c) 2019, 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 # Analyze 11 12 The worklist should not be cleared during an analyze phase. 13 Any mutant that has been removed in the source code will be automatically 14 removed from the worklist because the tables is setup with ON DELETE CASCADE. 15 16 Thus by not removing it old timeout mutants that need more work will be 17 "resumed". 18 19 # Test 20 21 TODO: describe the test phase and FSM 22 */ 23 module dextool.plugin.mutate.backend.test_mutant.timeout; 24 25 import logger = std.experimental.logger; 26 import std.exception : collectException; 27 28 import miniorm : spinSql; 29 30 import dextool.from; 31 import dextool.fsm; 32 33 import dextool.plugin.mutate.backend.database : Database, MutantTimeoutCtx, MutationStatusId; 34 import dextool.plugin.mutate.backend.type : Mutation; 35 36 @safe: 37 38 /// Reset the state of the timeout algorithm to its inital state. 39 void resetTimeoutContext(ref Database db) @trusted { 40 db.putMutantTimeoutCtx(MutantTimeoutCtx.init); 41 } 42 43 /// Calculate the timeout to use based on the context. 44 std_.datetime.Duration calculateTimeout(const long iter, std_.datetime.Duration base) pure nothrow @nogc { 45 import core.time : dur; 46 import std.math : sqrt; 47 48 static immutable double constant_factor = 1.5; 49 static immutable double scale_factor = 2.0; 50 const double n = iter; 51 52 const double scale = constant_factor + sqrt(n) * scale_factor; 53 return (1L + (cast(long)(base.total!"msecs" * scale))).dur!"msecs"; 54 } 55 56 /** Update the status of a mutant. 57 * 58 * If the mutant is `timeout` then it will be added to the worklist if the 59 * mutation testing is in the initial phase. 60 * 61 * If it has progressed beyond the init phase then it depends on if the local 62 * iteration variable of *this* instance of dextool matches the one in the 63 * database. This ensures that all instances that work on the same database is 64 * in-sync with each other. 65 * 66 * Params: 67 * db = database to use 68 * id = ? 69 * st = ? 70 * usedIter = the `iter` value that was used to test the mutant 71 */ 72 void updateMutantStatus(ref Database db, const MutationStatusId id, 73 const Mutation.Status st, const long usedIter) @trusted { 74 import std.typecons : Yes; 75 76 const ctx = db.getMutantTimeoutCtx; 77 78 final switch (ctx.state) with (MutantTimeoutCtx.State) { 79 case init_: 80 if (st == Mutation.Status.timeout) 81 db.putMutantInTimeoutWorklist(id); 82 db.updateMutationStatus(id, st, Yes.updateTs); 83 break; 84 case running: 85 if (usedIter == ctx.iter) { 86 db.updateMutationStatus(id, st, Yes.updateTs); 87 } 88 break; 89 case done: 90 break; 91 } 92 } 93 94 /** FSM for handling mutants during the test phase. 95 */ 96 struct TimeoutFsm { 97 @safe: 98 99 static struct Init { 100 } 101 102 static struct ResetWorkList { 103 } 104 105 static struct UpdateCtx { 106 } 107 108 static struct Running { 109 } 110 111 static struct Purge { 112 // worklist items and if they have changed or not 113 enum Event { 114 changed, 115 same 116 } 117 118 Event ev; 119 } 120 121 static struct Done { 122 } 123 124 static struct ClearWorkList { 125 } 126 127 static struct Stop { 128 } 129 130 /// Data used by all states. 131 static struct Global { 132 MutantTimeoutCtx ctx; 133 Mutation.Kind[] kinds; 134 Database* db; 135 } 136 137 static struct Output { 138 /// The current iteration through the timeout algorithm. 139 long iter; 140 /// When the testing of all timeouts are done, e.g. the state is "done". 141 bool done; 142 } 143 144 /// Output that may be used. 145 Output output; 146 147 private { 148 Fsm!(Init, ResetWorkList, UpdateCtx, Running, Purge, Done, ClearWorkList, Stop) fsm; 149 Global global; 150 } 151 152 this(const Mutation.Kind[] kinds) nothrow { 153 global.kinds = kinds.dup; 154 } 155 156 void execute(ref Database db) @trusted { 157 execute_(this, &db); 158 } 159 160 static void execute_(ref TimeoutFsm self, Database* db) @trusted { 161 self.global.db = db; 162 163 auto t = db.transaction; 164 self.global.ctx = db.getMutantTimeoutCtx; 165 166 // force the local state to match the starting point in the ctx 167 // (database). 168 final switch (self.global.ctx.state) with (MutantTimeoutCtx) { 169 case State.init_: 170 self.fsm.state = fsm(Init.init); 171 break; 172 case State.running: 173 self.fsm.state = fsm(Running.init); 174 break; 175 case State.done: 176 self.fsm.state = fsm(Done.init); 177 break; 178 } 179 180 // act on the inital state 181 try { 182 self.fsm.act!self; 183 } catch (Exception e) { 184 logger.warning(e.msg).collectException; 185 } 186 187 while (!self.fsm.isState!Stop) { 188 try { 189 step(self, *db); 190 } catch (Exception e) { 191 logger.warning(e.msg).collectException; 192 } 193 } 194 195 db.putMutantTimeoutCtx(self.global.ctx); 196 t.commit; 197 198 self.output.iter = self.global.ctx.iter; 199 } 200 201 private static void step(ref TimeoutFsm self, ref Database db) @safe { 202 bool noUnknown() { 203 return db.unknownSrcMutants(self.global.kinds, null).count == 0; 204 } 205 206 self.fsm.next!((Init a) { 207 if (noUnknown) 208 return fsm(ResetWorkList.init); 209 return fsm(Stop.init); 210 }, (ResetWorkList a) => fsm(UpdateCtx.init), (UpdateCtx a) => fsm(Running.init), (Running a) { 211 if (noUnknown) 212 return fsm(Purge.init); 213 return fsm(Stop.init); 214 }, (Purge a) { 215 final switch (a.ev) with (Purge.Event) { 216 case changed: 217 if (self.global.ctx.iter == MaxTimeoutIterations) { 218 return fsm(ClearWorkList.init); 219 } 220 return fsm(ResetWorkList.init); 221 case same: 222 return fsm(ClearWorkList.init); 223 } 224 }, (ClearWorkList a) => fsm(Done.init), (Done a) { 225 if (noUnknown) 226 return fsm(Stop.init); 227 // happens if an operation is performed that changes the status of 228 // already tested mutants to unknown. 229 return fsm(Running.init); 230 }, (Stop a) => fsm(a),); 231 232 self.fsm.act!self; 233 } 234 235 void opCall(Init) { 236 global.ctx = MutantTimeoutCtx.init; 237 output.done = false; 238 } 239 240 void opCall(ResetWorkList) { 241 global.db.resetMutantTimeoutWorklist; 242 } 243 244 void opCall(UpdateCtx) { 245 global.ctx.iter += 1; 246 global.ctx.worklistCount = global.db.countMutantTimeoutWorklist; 247 } 248 249 void opCall(Running) { 250 global.ctx.state = MutantTimeoutCtx.State.running; 251 output.done = false; 252 } 253 254 void opCall(ref Purge data) { 255 global.db.reduceMutantTimeoutWorklist; 256 257 if (global.db.countMutantTimeoutWorklist == global.ctx.worklistCount) 258 data.ev = Purge.Event.same; 259 else 260 data.ev = Purge.Event.changed; 261 } 262 263 void opCall(Done) { 264 global.ctx.state = MutantTimeoutCtx.State.done; 265 output.done = true; 266 } 267 268 void opCall(ClearWorkList) { 269 global.db.clearMutantTimeoutWorklist; 270 } 271 272 void opCall(Stop) { 273 } 274 } 275 276 private: 277 278 // If the mutants has been tested 3 times it should be good enough. Sometimes 279 // there are so many timeout that it would feel like the tool just end up in an 280 // infinite loop. Maybe this should be moved so it is user configurable in the 281 // future. 282 immutable MaxTimeoutIterations = 3;