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