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