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.mutantApi.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.testCaseApi
177                 .getDetectedTestCases
178                 .filter!(a => !matchFirst(a.name, re).empty)
179                 .map!(a => tuple!("tc", "id")(a, db.testCaseApi.getTestCaseId(a)))
180                 .filter!(a => !a.id.isNull)) {
181             logger.info("Removing ", a.tc);
182             db.testCaseApi.removeTestCase(a.id.get);
183         }
184 
185         trans.commit;
186     } catch (Exception e) {
187         logger.error(e.msg).collectException;
188         return ExitStatusType.Errors;
189     }
190 
191     return ExitStatusType.Ok;
192 }
193 
194 ExitStatusType resetTestCase(ref Database db, const Regex!char re) @trusted nothrow {
195     import std.typecons : tuple;
196 
197     try {
198         auto trans = db.transaction;
199 
200         foreach (a; db.testCaseApi
201                 .getDetectedTestCases
202                 .filter!(a => !matchFirst(a.name, re).empty)
203                 .map!(a => tuple!("tc", "id")(a, db.testCaseApi.getTestCaseId(a)))
204                 .filter!(a => !a.id.isNull)) {
205             logger.info("Resetting ", a.tc);
206             db.testCaseApi.resetTestCaseId(a.id.get);
207         }
208 
209         trans.commit;
210     } catch (Exception e) {
211         logger.error(e.msg).collectException;
212         return ExitStatusType.Errors;
213     }
214 
215     return ExitStatusType.Ok;
216 }
217 
218 ExitStatusType markMutant(ref Database db, MutationId id, const Mutation.Kind[] kinds,
219         const Mutation.Status status, string rationale, FilesysIO fio) @trusted nothrow {
220     import std.format : format;
221     import std..string : strip;
222     import dextool.plugin.mutate.backend.database : Rationale;
223     import dextool.plugin.mutate.backend.report.utility : window;
224 
225     if (rationale.empty) {
226         logger.error("The rationale must be set").collectException;
227         return ExitStatusType.Errors;
228     }
229 
230     try {
231         auto trans = db.transaction;
232 
233         auto mut = db.mutantApi.getMutation(id);
234         if (mut.isNull) {
235             logger.errorf("Mutant with ID %s do not exist", id.get);
236             return ExitStatusType.Errors;
237         }
238 
239         // because getMutation worked we know the ID is valid thus no need to
240         // check the return values when it or derived values are used.
241 
242         const st_id = db.mutantApi.getMutationStatusId(id).get;
243         const checksum = db.mutantApi.getChecksum(st_id).get;
244 
245         const txt = () {
246             auto tmp = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)),
247                     mut.get.mp.offset, db.mutantApi.getKind(id), mut.get.lang);
248             return window(format!"'%s'->'%s'"(tmp.original.strip, tmp.mutation.strip), 30);
249         }();
250 
251         db.markMutantApi.markMutant(id, mut.get.file, mut.get.sloc, st_id,
252                 checksum, status, Rationale(rationale), txt);
253 
254         db.mutantApi.updateMutationStatus(st_id, status, ExitStatus(0));
255 
256         logger.infof(`Mutant %s marked with status %s and rationale %s`, id.get, status, rationale);
257 
258         trans.commit;
259         return ExitStatusType.Ok;
260     } catch (Exception e) {
261         logger.trace(e).collectException;
262         logger.error(e.msg).collectException;
263     }
264     return ExitStatusType.Errors;
265 }
266 
267 ExitStatusType removeMarkedMutant(ref Database db, MutationId id) @trusted nothrow {
268     try {
269         auto trans = db.transaction;
270 
271         // MutationStatusId used as check, removal of marking and updating status to unknown
272         const st_id = db.mutantApi.getMutationStatusId(id);
273         if (st_id.isNull) {
274             logger.errorf("Mutant with ID %s do not exist", id.get);
275             return ExitStatusType.Errors;
276         }
277 
278         if (db.markMutantApi.isMarked(id)) {
279             db.markMutantApi.removeMarkedMutant(st_id.get);
280             db.mutantApi.updateMutationStatus(st_id.get, Mutation.Status.unknown, ExitStatus(0));
281             logger.infof("Removed marking for mutant %s.", id);
282         } else {
283             logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id.get);
284         }
285 
286         trans.commit;
287         return ExitStatusType.Ok;
288     } catch (Exception e) {
289         logger.error(e.msg).collectException;
290     }
291     return ExitStatusType.Errors;
292 }
293 
294 ExitStatusType compact(ref Database db) @trusted nothrow {
295     try {
296         logger.info("Running a SQL vacuum on the database");
297         db.vacuum;
298         return ExitStatusType.Ok;
299     } catch (Exception e) {
300         logger.error(e.msg).collectException;
301     }
302     return ExitStatusType.Errors;
303 }
304 
305 ExitStatusType stopTimeoutTest(ref Database db) @trusted nothrow {
306     import dextool.plugin.mutate.backend.database : Database, MutantTimeoutCtx;
307     import dextool.plugin.mutate.backend.test_mutant.timeout : MaxTimeoutIterations;
308 
309     try {
310         logger.info("Forcing the testing of timeout mutants to stop");
311         auto t = db.transaction;
312 
313         db.timeoutApi.resetMutantTimeoutWorklist(Mutation.Status.timeout);
314         db.timeoutApi.clearMutantTimeoutWorklist;
315         db.worklistApi.clearWorklist;
316 
317         MutantTimeoutCtx ctx;
318         ctx.iter = MaxTimeoutIterations;
319         ctx.state = MutantTimeoutCtx.State.done;
320         db.timeoutApi.putMutantTimeoutCtx(ctx);
321 
322         t.commit;
323         return ExitStatusType.Ok;
324     } catch (Exception e) {
325         logger.error(e.msg).collectException;
326     }
327     return ExitStatusType.Errors;
328 }
329 
330 ExitStatusType clearWorklist(ref Database db) @trusted nothrow {
331     try {
332         logger.info("Clearing the mutant worklist");
333         db.clearWorklist;
334         return ExitStatusType.Ok;
335     } catch (Exception e) {
336         logger.error(e.msg).collectException;
337     }
338     return ExitStatusType.Errors;
339 }