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 std.exception : collectException;
15 import logger = std.experimental.logger;
16 
17 import dextool.type;
18 
19 import dextool.plugin.mutate.type : MutationKind, AdminOperation;
20 import dextool.plugin.mutate.backend.database : Database, MutationId;
21 import dextool.plugin.mutate.backend.type : Mutation, Offset;
22 import dextool.plugin.mutate.backend.interface_ : FilesysIO;
23 import dextool.plugin.mutate.backend.generate_mutant : makeMutationText;
24 
25 auto makeAdmin() {
26     return BuildAdmin();
27 }
28 
29 private:
30 
31 import std.regex : regex, Regex;
32 
33 struct BuildAdmin {
34 @safe:
35 nothrow:
36     private struct InternalData {
37         bool errorInData;
38 
39         AdminOperation admin_op;
40         Mutation.Kind[] kinds;
41         Mutation.Status status;
42         Mutation.Status to_status;
43         Regex!char test_case_regex;
44         MutationId mutant_id;
45         string mutant_rationale;
46         FilesysIO fio;
47     }
48 
49     private InternalData data;
50 
51     auto operation(AdminOperation v) {
52         data.admin_op = v;
53         return this;
54     }
55 
56     auto mutations(MutationKind[] v) {
57         import dextool.plugin.mutate.backend.utility;
58 
59         data.kinds = toInternal(v);
60         return this;
61     }
62 
63     auto fromStatus(Mutation.Status v) {
64         data.status = v;
65         return this;
66     }
67 
68     auto toStatus(Mutation.Status v) {
69         data.to_status = v;
70         return this;
71     }
72 
73     auto testCaseRegex(string v) {
74         try {
75             data.test_case_regex = regex(v);
76         } catch (Exception e) {
77             logger.error(e.msg).collectException;
78             data.errorInData = true;
79         }
80         return this;
81     }
82 
83     auto markMutantData(long v, string s, FilesysIO f) {
84         data.mutant_id = v;
85         data.mutant_rationale = s;
86         data.fio = f;
87         return this;
88     }
89 
90     ExitStatusType run(ref Database db) {
91         if (data.errorInData) {
92             logger.error("Invalid parameters").collectException;
93             return ExitStatusType.Errors;
94         }
95 
96         final switch (data.admin_op) {
97         case AdminOperation.none:
98             logger.error("No admin operation specified").collectException;
99             return ExitStatusType.Errors;
100         case AdminOperation.resetMutant:
101             return resetMutant(db, data.kinds,
102                     data.status, data.to_status);
103         case AdminOperation.removeMutant:
104             return removeMutant(db, data.kinds);
105         case AdminOperation.removeTestCase:
106             return removeTestCase(db,
107                     data.kinds, data.test_case_regex);
108         case AdminOperation.markMutant:
109             return markMutant(db, data.mutant_id,
110                     data.kinds, data.to_status, data.mutant_rationale, data.fio);
111         case AdminOperation.removeMarkedMutant:
112             return removeMarkedMutant(db, data.mutant_id);
113         }
114     }
115 }
116 
117 ExitStatusType resetMutant(ref Database db, const Mutation.Kind[] kinds,
118         Mutation.Status status, Mutation.Status to_status) @safe nothrow {
119     try {
120         db.resetMutant(kinds, status, to_status);
121     } catch (Exception e) {
122         logger.error(e.msg).collectException;
123         return ExitStatusType.Errors;
124     }
125 
126     return ExitStatusType.Ok;
127 }
128 
129 ExitStatusType removeMutant(ref Database db, const Mutation.Kind[] kinds) @safe nothrow {
130     try {
131         db.removeMutant(kinds);
132     } catch (Exception e) {
133         logger.error(e.msg).collectException;
134         return ExitStatusType.Errors;
135     }
136 
137     return ExitStatusType.Ok;
138 }
139 
140 ExitStatusType removeTestCase(ref Database db, const Mutation.Kind[] kinds, const Regex!char regex) @safe nothrow {
141     try {
142         db.removeTestCase(regex, kinds);
143     } catch (Exception e) {
144         logger.error(e.msg).collectException;
145         return ExitStatusType.Errors;
146     }
147     return ExitStatusType.Ok;
148 }
149 
150 ExitStatusType markMutant(ref Database db, MutationId id, const Mutation.Kind[] kinds,
151         const Mutation.Status status, string rationale, FilesysIO fio) @trusted nothrow {
152     import dextool.plugin.mutate.backend.database : Rationale;
153 
154     try {
155         auto trans = db.transaction;
156 
157         auto mut = db.getMutation(id);
158         if (mut.isNull) {
159             logger.errorf("Failure when marking mutant: %s", id);
160             return ExitStatusType.Errors;
161         }
162 
163         // because getMutation worked we know the ID is valid thus no need to
164         // check the return values when it or derived values are used.
165 
166         const st_id = db.getMutationStatusId(id).get;
167         const checksum = db.getChecksum(st_id).get;
168 
169         // assume that mutant has kind
170         const txt = makeMutationText(fio.makeInput(AbsolutePath(mut.get.file,
171                 DirName(fio.getOutputDir))), Offset(mut.get.sloc.line,
172                 mut.get.sloc.column), db.getKind(id), mut.get.lang).mutation;
173 
174         db.markMutant(id, mut.get.file, mut.get.sloc, st_id, checksum, status,
175                 Rationale(rationale), txt.idup);
176 
177         db.updateMutationStatus(st_id, status);
178 
179         logger.infof(`Mutant %s marked with status %s and rationale %s`, id, status, rationale);
180 
181         trans.commit;
182         return ExitStatusType.Ok;
183     } catch (Exception e) {
184         logger.trace(e).collectException;
185         logger.error(e.msg).collectException;
186     }
187     return ExitStatusType.Errors;
188 }
189 
190 ExitStatusType removeMarkedMutant(ref Database db, MutationId id) @trusted nothrow {
191     try {
192         auto trans = db.transaction;
193 
194         // MutationStatusId used as check, removal of marking and updating status to unknown
195         const st_id = db.getMutationStatusId(id);
196         if (st_id.isNull) {
197             logger.errorf("Failure when removing marked mutant: %s", id);
198             return ExitStatusType.Errors;
199         }
200 
201         if (db.isMarked(id)) {
202             db.removeMarkedMutant(st_id.get);
203             db.updateMutationStatus(st_id.get, Mutation.Status.unknown);
204             logger.infof("Removed marking for mutant %s.", id);
205         } else {
206             logger.errorf("Failure when removing marked mutant (mutant %s is not marked)", id);
207         }
208 
209         trans.commit;
210         return ExitStatusType.Ok;
211     } catch (Exception e) {
212         logger.error(e.msg).collectException;
213     }
214     return ExitStatusType.Errors;
215 }