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