1 /**
2 Copyright: Copyright (c) 2020, 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 module dextool.plugin.mutate.backend.test_mutant.schemata;
11 
12 import logger = std.experimental.logger;
13 import std.algorithm : sort, map;
14 import std.array : empty;
15 import std.conv : to;
16 import std.datetime : Duration;
17 import std.exception : collectException;
18 import std.typecons : Tuple;
19 
20 import process : DrainElement;
21 import sumtype;
22 
23 import dextool.fsm : Fsm, next, act, get, TypeDataMap;
24 
25 import dextool.plugin.mutate.backend.database : MutationStatusId, Database, spinSql;
26 import dextool.plugin.mutate.backend.interface_ : FilesysIO, Blob;
27 import dextool.plugin.mutate.backend.test_mutant.common;
28 import dextool.plugin.mutate.backend.test_mutant.test_cmd_runner;
29 import dextool.plugin.mutate.backend.type : Mutation, TestCase, Checksum;
30 import dextool.plugin.mutate.type : TestCaseAnalyzeBuiltin, ShellCommand;
31 
32 @safe:
33 
34 struct MutationTestResult {
35     import std.datetime : Duration;
36     import dextool.plugin.mutate.backend.database : MutationStatusId;
37     import dextool.plugin.mutate.backend.type : TestCase;
38 
39     MutationStatusId id;
40     Mutation.Status status;
41     Duration testTime;
42     TestCase[] testCases;
43 }
44 
45 struct SchemataTestDriver {
46     private {
47         /// True as long as the schemata driver is running.
48         bool isRunning_ = true;
49 
50         FilesysIO fio;
51 
52         Database* db;
53 
54         /// Runs the test commands.
55         TestRunner* runner;
56 
57         /// Result of testing the mutants.
58         MutationTestResult[] result_;
59     }
60 
61     static struct None {
62     }
63 
64     static struct Initialize {
65     }
66 
67     static struct Done {
68     }
69 
70     static struct NextMutantData {
71         /// Mutants to test.
72         MutationStatusId[] mutants;
73     }
74 
75     static struct NextMutant {
76         bool done;
77         MutationStatusId id;
78         Checksum checksum;
79     }
80 
81     static struct TestMutantData {
82         /// If the user has configured that the test cases should be analyzed.
83         bool hasTestCaseOutputAnalyzer;
84     }
85 
86     static struct TestMutant {
87         MutationStatusId id;
88         Checksum checksum;
89         MutationTestResult result;
90         DrainElement[] output;
91     }
92 
93     static struct TestCaseAnalyzeData {
94         TestCaseAnalyzer* testCaseAnalyzer;
95     }
96 
97     static struct TestCaseAnalyze {
98         MutationTestResult result;
99         DrainElement[] output;
100         bool unstableTests;
101     }
102 
103     static struct StoreResult {
104         MutationTestResult result;
105     }
106 
107     alias Fsm = dextool.fsm.Fsm!(None, Initialize, Done, NextMutant,
108             TestMutant, TestCaseAnalyze, StoreResult);
109     alias LocalStateDataT = Tuple!(TestMutantData, TestCaseAnalyzeData, NextMutantData);
110 
111     private {
112         Fsm fsm;
113         TypeDataMap!(LocalStateDataT, TestMutant, TestCaseAnalyze, NextMutant) local;
114     }
115 
116     this(FilesysIO fio, TestRunner* runner, Database* db,
117             TestCaseAnalyzer* testCaseAnalyzer, MutationStatusId[] mutants) {
118         this.fio = fio;
119         this.runner = runner;
120         this.db = db;
121         this.local.get!NextMutant.mutants = mutants;
122         this.local.get!TestCaseAnalyze.testCaseAnalyzer = testCaseAnalyzer;
123     }
124 
125     static void execute_(ref SchemataTestDriver self) @trusted {
126         self.fsm.next!((None a) => fsm(Initialize.init),
127                 (Initialize a) => fsm(NextMutant.init), (NextMutant a) {
128             if (a.done)
129                 return fsm(Done.init);
130             return fsm(TestMutant(a.id, a.checksum));
131         }, (TestMutant a) {
132             if (self.local.get!TestMutant.hasTestCaseOutputAnalyzer)
133                 return fsm(TestCaseAnalyze(a.result, a.output));
134             return fsm(StoreResult(a.result));
135         }, (TestCaseAnalyze a) {
136             if (a.unstableTests)
137                 return fsm(NextMutant.init);
138             return fsm(StoreResult(a.result));
139         }, (StoreResult a) => fsm(NextMutant.init), (Done a) => fsm(a));
140 
141         self.fsm.act!(self);
142     }
143 
144 nothrow:
145 
146     MutationTestResult[] result() {
147         return result_;
148     }
149 
150     void execute() {
151         try {
152             execute_(this);
153         } catch (Exception e) {
154             logger.warning(e.msg).collectException;
155         }
156     }
157 
158     bool isRunning() {
159         return isRunning_;
160     }
161 
162     void opCall(None data) {
163     }
164 
165     void opCall(Initialize data) {
166     }
167 
168     void opCall(Done data) {
169         isRunning_ = false;
170     }
171 
172     void opCall(ref NextMutant data) {
173         data.done = local.get!NextMutant.mutants.empty;
174 
175         if (!local.get!NextMutant.mutants.empty) {
176             data.id = local.get!NextMutant.mutants[$ - 1];
177             local.get!NextMutant.mutants = local.get!NextMutant.mutants[0 .. $ - 1];
178             data.checksum = spinSql!(() { return db.getChecksum(data.id); });
179         }
180     }
181 
182     void opCall(ref TestMutant data) {
183         import std.datetime.stopwatch : StopWatch, AutoStart;
184         import dextool.plugin.mutate.backend.generate_mutant : makeMutationText;
185 
186         data.result.id = data.id;
187 
188         auto id = spinSql!(() { return db.getMutationId(data.id); }).get;
189         auto entry = spinSql!(() { return db.getMutation(id); }).get;
190 
191         try {
192             auto original = fio.makeInput(fio.toAbsoluteRoot(entry.file));
193             auto txt = makeMutationText(original, entry.mp.offset,
194                     entry.mp.mutations[0].kind, entry.lang);
195             logger.infof("%s from '%s' to '%s' in %s:%s:%s", data.id, txt.original,
196                     txt.mutation, entry.file, entry.sloc.line, entry.sloc.column);
197         } catch (Exception e) {
198             logger.info(e.msg).collectException;
199         }
200 
201         static immutable idKey = "DEXTOOL_MUTID";
202         runner.env[idKey] = data.checksum.c0.to!string;
203         scope (exit)
204             runner.env.remove(idKey);
205 
206         auto sw = StopWatch(AutoStart.yes);
207         auto res = runTester(*runner);
208         data.result.testTime = sw.peek;
209 
210         data.result.status = res.status;
211         data.output = res.output;
212 
213         logger.infof("%s %s (%s)", data.result.id, data.result.status,
214                 data.result.testTime).collectException;
215     }
216 
217     void opCall(ref TestCaseAnalyze data) {
218         try {
219             auto analyze = local.get!TestCaseAnalyze.testCaseAnalyzer.analyze(data.output);
220 
221             analyze.match!((TestCaseAnalyzer.Success a) {
222                 data.result.testCases = a.failed;
223             }, (TestCaseAnalyzer.Unstable a) {
224                 logger.warningf("Unstable test cases found: [%-(%s, %)]", a.unstable);
225                 logger.info(
226                     "As configured the result is ignored which will force the mutant to be re-tested");
227                 data.unstableTests = true;
228             }, (TestCaseAnalyzer.Failed a) {
229                 logger.warning("The parser that analyze the output from test case(s) failed");
230             });
231 
232             logger.infof(!data.result.testCases.empty, `%s killed by [%-(%s, %)]`,
233                     data.result.id, data.result.testCases.sort.map!"a.name").collectException;
234         } catch (Exception e) {
235             logger.warning(e.msg).collectException;
236         }
237     }
238 
239     void opCall(StoreResult data) {
240         result_ ~= data.result;
241     }
242 }