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