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 }