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 }