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.exception : collectException;
17 import std.regex : matchFirst;
18 
19 import dextool.type;
20 
21 import dextool.plugin.mutate.type : MutationKind, AdminOperation;
22 import dextool.plugin.mutate.backend.database : Database, MutationId;
23 import dextool.plugin.mutate.backend.type : Mutation, Offset;
24 import dextool.plugin.mutate.backend.interface_ : FilesysIO;
25 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText;
26 
27 auto makeAdmin() {
28     return BuildAdmin.init;
29 }
30 
31 private:
32 
33 import std.regex : regex, Regex;
34 
35 struct BuildAdmin {
36 @safe:
37 nothrow:
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     }
50 
51     private InternalData data;
52 
53     auto operation(AdminOperation v) {
54         data.admin_op = v;
55         return this;
56     }
57 
58     auto mutations(MutationKind[] v) {
59         import dextool.plugin.mutate.backend.utility;
60 
61         data.kinds = toInternal(v);
62         return this;
63     }
64 
65     auto fromStatus(Mutation.Status v) {
66         data.status = v;
67         return this;
68     }
69 
70     auto toStatus(Mutation.Status v) {
71         data.to_status = v;
72         return this;
73     }
74 
75     auto testCaseRegex(string v) {
76         try {
77             data.test_case_regex = regex(v);
78         } catch (Exception e) {
79             logger.error(e.msg).collectException;
80             data.errorInData = true;
81         }
82         return this;
83     }
84 
85     auto markMutantData(long v, string s, FilesysIO f) {
86         data.mutant_id = v;
87         data.mutant_rationale = s;
88         data.fio = f;
89         return this;
90     }
91 
92     ExitStatusType run(ref Database db) {
93         if (data.errorInData) {
94             logger.error("Invalid parameters").collectException;
95             return ExitStatusType.Errors;
96         }
97 
98         final switch (data.admin_op) {
99         case AdminOperation.none:
100             logger.error("No admin operation specified").collectException;
101             return ExitStatusType.Errors;
102         case AdminOperation.resetMutant:
103             return resetMutant(db, data.kinds,
104                     data.status, data.to_status);
105         case AdminOperation.removeMutant:
106             return removeMutant(db, data.kinds);
107         case AdminOperation.removeTestCase:
108             return removeTestCase(db, data.test_case_regex);
109         case AdminOperation.resetTestCase:
110             return resetTestCase(db, data.test_case_regex);
111         case AdminOperation.markMutant:
112             return markMutant(db, data.mutant_id,
113                     data.kinds, data.to_status, data.mutant_rationale, data.fio);
114         case AdminOperation.removeMarkedMutant:
115             return removeMarkedMutant(db, data.mutant_id);
116         case AdminOperation.compact:
117             return compact(db);
118         }
119     }
120 }
121 
122 ExitStatusType resetMutant(ref Database db, const Mutation.Kind[] kinds,
123         Mutation.Status status, Mutation.Status to_status) @safe nothrow {
124     try {
125         db.resetMutant(kinds, status, to_status);
126     } catch (Exception e) {
127         logger.error(e.msg).collectException;
128         return ExitStatusType.Errors;
129     }
130 
131     return ExitStatusType.Ok;
132 }
133 
134 ExitStatusType removeMutant(ref Database db, const Mutation.Kind[] kinds) @safe nothrow {
135     try {
136         db.removeMutant(kinds);
137     } catch (Exception e) {
138         logger.error(e.msg).collectException;
139         return ExitStatusType.Errors;
140     }
141 
142     return ExitStatusType.Ok;
143 }
144 
145 ExitStatusType removeTestCase(ref Database db, const Regex!char re) @trusted nothrow {
146     import std.typecons : tuple;
147 
148     try {
149         auto trans = db.transaction;
150 
151         foreach (a; db.getDetectedTestCases
152                 .filter!(a => !matchFirst(a.name, re).empty)
153                 .map!(a => tuple!("tc", "id")(a, db.getTestCaseId(a)))
154                 .filter!(a => !a.id.isNull)) {
155             logger.info("Removing ", a.tc);
156             db.removeTestCase(a.id.get);
157         }
158 
159         trans.commit;
160     } catch (Exception e) {
161         logger.error(e.msg).collectException;
162         return ExitStatusType.Errors;
163     }
164 
165     return ExitStatusType.Ok;
166 }
167 
168 ExitStatusType resetTestCase(ref Database db, const Regex!char re) @trusted nothrow {
169     import std.typecons : tuple;
170 
171     try {
172         auto trans = db.transaction;
173 
174         foreach (a; db.getDetectedTestCases
175                 .filter!(a => !matchFirst(a.name, re).empty)
176                 .map!(a => tuple!("tc", "id")(a, db.getTestCaseId(a)))
177                 .filter!(a => !a.id.isNull)) {
178             logger.info("Resetting ", a.tc);
179             db.resetTestCaseId(a.id.get);
180         }
181 
182         trans.commit;
183     } catch (Exception e) {
184         logger.error(e.msg).collectException;
185         return ExitStatusType.Errors;
186     }
187 
188     return ExitStatusType.Ok;
189 }
190 
191 ExitStatusType markMutant(ref Database db, MutationId id, const Mutation.Kind[] kinds,
192         const Mutation.Status status, string rationale, FilesysIO fio) @trusted nothrow {
193     import dextool.plugin.mutate.backend.database : Rationale;
194 
195     try {
196         auto trans = db.transaction;
197 
198         auto mut = db.getMutation(id);
199         if (mut.isNull) {
200             logger.errorf("Failure when marking mutant: %s", id);
201             return ExitStatusType.Errors;
202         }
203 
204         // because getMutation worked we know the ID is valid thus no need to
205         // check the return values when it or derived values are used.
206 
207         const st_id = db.getMutationStatusId(id).get;
208         const checksum = db.getChecksum(st_id).get;
209 
210         // assume that mutant has kind
211         const txt = makeMutationText(fio.makeInput(fio.toAbsoluteRoot(mut.get.file)),
212                 mut.get.mp.offset, db.getKind(id), mut.get.lang).mutation;
213 
214         db.markMutant(id, mut.get.file, mut.get.sloc, st_id, checksum, status,
215                 Rationale(rationale), txt.idup);
216 
217         db.updateMutationStatus(st_id, status);
218 
219         logger.infof(`Mutant %s marked with status %s and rationale %s`, id, status, rationale);
220 
221         trans.commit;
222         return ExitStatusType.Ok;
223     } catch (Exception e) {
224         logger.trace(e).collectException;
225         logger.error(e.msg).collectException;
226     }
227     return ExitStatusType.Errors;
228 }
229 
230 ExitStatusType removeMarkedMutant(ref Database db, MutationId id) @trusted nothrow {
231     try {
232         auto trans = db.transaction;
233 
234         // MutationStatusId used as check, removal of marking and updating status to unknown
235         const st_id = db.getMutationStatusId(id);
236         if (st_id.isNull) {
237             logger.errorf("Failure when removing marked mutant: %s", id);
238             return ExitStatusType.Errors;
239         }
240 
241         if (db.isMarked(id)) {
242             db.removeMarkedMutant(st_id.get);
243             db.updateMutationStatus(st_id.get, Mutation.Status.unknown);
244             logger.infof("Removed marking for mutant %s.", id);
245         } else {
246             logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id);
247         }
248 
249         trans.commit;
250         return ExitStatusType.Ok;
251     } catch (Exception e) {
252         logger.error(e.msg).collectException;
253     }
254     return ExitStatusType.Errors;
255 }
256 
257 ExitStatusType compact(ref Database db) @trusted nothrow {
258     try {
259         logger.info("Running a SQL vacuum on the database");
260         db.run("VACUUM");
261         return ExitStatusType.Ok;
262     } catch (Exception e) {
263         logger.error(e.msg).collectException;
264     }
265     return ExitStatusType.Errors;
266 }