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, MutationStatusId;
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         MutationStatusId 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(MutationStatusId 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, MutationStatusId 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, toChecksum;
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 txt = () {
243             auto tmp = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)),
244                     mut.get.mp.offset, db.mutantApi.getKind(id), mut.get.lang);
245             return window(format!"'%s'->'%s'"(tmp.original.strip, tmp.mutation.strip), 30);
246         }();
247 
248         db.markMutantApi.mark(mut.get.file, mut.get.sloc, id, toChecksum(id),
249                 status, Rationale(rationale), txt);
250 
251         db.mutantApi.update(id, status, ExitStatus(0));
252 
253         logger.infof(`Mutant %s marked with status %s and rationale %s`, id.get, status, rationale);
254 
255         trans.commit;
256         return ExitStatusType.Ok;
257     } catch (Exception e) {
258         logger.trace(e).collectException;
259         logger.error(e.msg).collectException;
260     }
261     return ExitStatusType.Errors;
262 }
263 
264 ExitStatusType removeMarkedMutant(ref Database db, MutationStatusId id) @trusted nothrow {
265     try {
266         auto trans = db.transaction;
267 
268         // MutationStatusId used as check, removal of marking and updating status to unknown
269         if (db.markMutantApi.isMarked(id)) {
270             db.markMutantApi.remove(id);
271             db.mutantApi.update(id, Mutation.Status.unknown, ExitStatus(0));
272             logger.infof("Removed marking for mutant %s.", id);
273         } else {
274             logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id.get);
275         }
276 
277         trans.commit;
278         return ExitStatusType.Ok;
279     } catch (Exception e) {
280         logger.error(e.msg).collectException;
281     }
282     return ExitStatusType.Errors;
283 }
284 
285 ExitStatusType compact(ref Database db) @trusted nothrow {
286     try {
287         logger.info("Running a SQL vacuum on the database");
288         db.vacuum;
289         return ExitStatusType.Ok;
290     } catch (Exception e) {
291         logger.error(e.msg).collectException;
292     }
293     return ExitStatusType.Errors;
294 }
295 
296 ExitStatusType stopTimeoutTest(ref Database db) @trusted nothrow {
297     import dextool.plugin.mutate.backend.database : Database, MutantTimeoutCtx;
298     import dextool.plugin.mutate.backend.test_mutant.timeout : MaxTimeoutIterations;
299 
300     try {
301         logger.info("Forcing the testing of timeout mutants to stop");
302         auto t = db.transaction;
303 
304         db.timeoutApi.resetMutantTimeoutWorklist(Mutation.Status.timeout);
305         db.timeoutApi.clearMutantTimeoutWorklist;
306         db.worklistApi.clear;
307 
308         MutantTimeoutCtx ctx;
309         ctx.iter = MaxTimeoutIterations;
310         ctx.state = MutantTimeoutCtx.State.done;
311         db.timeoutApi.put(ctx);
312 
313         t.commit;
314         return ExitStatusType.Ok;
315     } catch (Exception e) {
316         logger.error(e.msg).collectException;
317     }
318     return ExitStatusType.Errors;
319 }
320 
321 ExitStatusType clearWorklist(ref Database db) @trusted nothrow {
322     try {
323         logger.info("Clearing the mutant worklist");
324         db.clearWorklist;
325         return ExitStatusType.Ok;
326     } catch (Exception e) {
327         logger.error(e.msg).collectException;
328     }
329     return ExitStatusType.Errors;
330 }