1 /** 2 Copyright: Copyright (c) 2018-2021, 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 the a basic database interface that have minimal 11 dependencies on internal modules. It is intended to be reusable from the test 12 suite. 13 14 The only acceptable dependency are: 15 * ../type.d 16 * ..backend/type.d 17 * ../database/type.d 18 * ../database/schema.d 19 */ 20 module dextool.plugin.mutate.backend.database.standalone; 21 22 import core.time : Duration, dur; 23 import logger = std.experimental.logger; 24 import std.algorithm : copy, map, joiner, filter; 25 import std.array : Appender, appender, array, empty; 26 import std.conv : to; 27 import std.datetime : SysTime, Clock; 28 import std.exception : collectException; 29 import std.format : format; 30 import std.path : relativePath; 31 import std.regex : Regex, matchFirst; 32 import std.typecons : Nullable, Flag, No; 33 34 import d2sqlite3 : SqlDatabase = Database; 35 import miniorm : Miniorm, select, insert, insertOrReplace, delete_, 36 insertOrIgnore, toSqliteDateTime, fromSqLiteDateTime, Bind; 37 import my.named_type; 38 import my.optional; 39 import my.term_color; 40 import my.set; 41 42 import dextool.type : AbsolutePath, Path, ExitStatusType; 43 44 import dextool.plugin.mutate.backend.database.schema; 45 import dextool.plugin.mutate.backend.database.type; 46 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum, Language, Offset; 47 import dextool.plugin.mutate.type : MutationOrder; 48 49 /** Database wrapper with minimal dependencies. 50 */ 51 struct Database { 52 Miniorm db; 53 alias db this; 54 55 /** Create a database by either opening an existing or initializing a new. 56 * 57 * Params: 58 * db = path to the database 59 */ 60 static auto make(string db) @safe { 61 return Database(initializeDB(db)); 62 } 63 64 bool isToolVersionDifferent(ToolVersion compareTo) @trusted { 65 foreach (a; db.run(select!DextoolVersionTable)) { 66 return a.checksum != compareTo.get; 67 } 68 // if there is no tool version recorded then assume it is different. 69 return true; 70 } 71 72 /// Update the version of the tool. 73 void updateToolVersion(const ToolVersion tv) @trusted { 74 db.run(delete_!DextoolVersionTable); 75 db.run(insert!DextoolVersionTable, DextoolVersionTable(tv.get)); 76 } 77 78 /** Add all mutants with the specific status to the worklist. 79 */ 80 void updateWorklist(const Mutation.Kind[] kinds, const Mutation.Status status, 81 const long basePrio = 100, const MutationOrder userOrder = MutationOrder.random) @trusted { 82 const order = fromOrder(userOrder); 83 84 const sql = format!"INSERT OR IGNORE INTO %s (id,prio) 85 SELECT t1.id,%s FROM %s t0, %s t1 WHERE t0.kind IN (%(%s,%)) AND 86 t0.st_id = t1.id AND 87 t1.status = :status 88 "(mutantWorklistTable, order, 89 mutationTable, mutationStatusTable, kinds.map!(a => cast(int) a)); 90 auto stmt = db.prepare(sql); 91 stmt.get.bind(":status", cast(long) status); 92 stmt.get.bind(":base_prio", basePrio); 93 stmt.get.execute; 94 } 95 96 /// Add a mutant to the worklist. 97 void addToWorklist(const MutationStatusId id, const long basePrio = 0, 98 const MutationOrder userOrder = MutationOrder.consecutive) @trusted { 99 const order = fromOrder(userOrder); 100 const sql = format!"INSERT OR REPLACE INTO %s (id,prio) 101 SELECT t1.id,%s FROM %s t1 WHERE t1.id = :id 102 "(mutantWorklistTable, order, mutationStatusTable); 103 auto stmt = db.prepare(sql); 104 stmt.get.bind(":id", id.get); 105 stmt.get.bind(":base_prio", basePrio); 106 stmt.get.execute; 107 } 108 109 /// Remove a mutant from the worklist. 110 void removeFromWorklist(const MutationStatusId id) @trusted { 111 static immutable sql = format!"DELETE FROM %1$s WHERE id = :id"(mutantWorklistTable); 112 auto stmt = db.prepare(sql); 113 stmt.get.bind(":id", id.get); 114 stmt.get.execute; 115 } 116 117 void clearWorklist() @trusted { 118 static immutable sql = format!"DELETE FROM %1$s"(mutantWorklistTable); 119 auto stmt = db.prepare(sql); 120 stmt.get.execute; 121 } 122 123 long getWorklistCount() @trusted { 124 static immutable sql = format!"SELECT count(*) FROM %1$s"(mutantWorklistTable); 125 auto stmt = db.prepare(sql); 126 auto res = stmt.get.execute; 127 return res.oneValue!long; 128 } 129 130 /// If the file has already been analyzed. 131 bool isAnalyzed(const Path p) @trusted { 132 auto stmt = db.prepare("SELECT count(*) FROM files WHERE path=:path LIMIT 1"); 133 stmt.get.bind(":path", cast(string) p); 134 auto res = stmt.get.execute; 135 return res.oneValue!long != 0; 136 } 137 138 /// If the file has already been analyzed. 139 bool isAnalyzed(const Path p, const Checksum cs) @trusted { 140 auto stmt = db.prepare( 141 "SELECT count(*) FROM files WHERE path=:path AND checksum0=:cs0 AND checksum1=:cs1 LIMIT 1"); 142 stmt.get.bind(":path", cast(string) p); 143 stmt.get.bind(":cs0", cast(long) cs.c0); 144 stmt.get.bind(":cs1", cast(long) cs.c1); 145 auto res = stmt.get.execute; 146 return res.oneValue!long != 0; 147 } 148 149 bool exists(MutationStatusId id) { 150 static immutable s = format!"SELECT COUNT(*) FROM %s WHERE id=:id LIMIT 1"( 151 mutationStatusTable); 152 auto stmt = db.prepare(s); 153 stmt.get.bind(":id", cast(long) id); 154 auto res = stmt.get.execute; 155 return res.oneValue!long == 0; 156 } 157 158 bool isMarked(MutationId id) @trusted { 159 static immutable s = format!"SELECT COUNT(*) FROM %s WHERE st_id IN 160 (SELECT st_id FROM %s WHERE id=:id)"( 161 markedMutantTable, mutationTable); 162 auto stmt = db.prepare(s); 163 stmt.get.bind(":id", cast(long) id); 164 auto res = stmt.get.execute; 165 return res.oneValue!long != 0; 166 } 167 168 /// All marked mutants whom have a mutation status checksum that has been removed from the database. 169 MarkedMutant[] getLostMarkings() @trusted { 170 static immutable sql = format!"SELECT checksum0 FROM %s 171 WHERE 172 checksum0 NOT IN (SELECT checksum0 FROM %s)"( 173 markedMutantTable, mutationStatusTable); 174 175 auto stmt = db.prepare(sql); 176 auto app = appender!(MarkedMutant[])(); 177 foreach (res; stmt.get.execute) { 178 foreach (m; db.run(select!MarkedMutantTbl.where("checksum0 = :cs0", 179 Bind("cs0")), res.peek!long(0))) { 180 app.put(.make(m)); 181 } 182 } 183 184 return app.data; 185 } 186 187 Nullable!FileId getFileId(const Path p) @trusted { 188 static immutable sql = format("SELECT id FROM %s WHERE path=:path", filesTable); 189 auto stmt = db.prepare(sql); 190 stmt.get.bind(":path", p.toString); 191 auto res = stmt.get.execute; 192 193 typeof(return) rval; 194 if (!res.empty) 195 rval = FileId(res.oneValue!long); 196 return rval; 197 } 198 199 /// Returns: the path ID for the mutant. 200 Nullable!FileId getFileId(const MutationId id) @trusted { 201 static immutable sql = format("SELECT t1.file_id 202 FROM %s t0, %s t1 203 WHERE t0.id = :id AND t0.mp_id = t1.id", 204 mutationTable, mutationPointTable); 205 auto stmt = db.prepare(sql); 206 stmt.get.bind(":id", cast(long) id); 207 208 typeof(return) rval; 209 foreach (ref r; stmt.get.execute) 210 rval = FileId(r.peek!long(0)); 211 return rval; 212 } 213 214 /// Returns: the file path that the id correspond to. 215 Nullable!Path getFile(const FileId id) @trusted { 216 static immutable sql = format("SELECT path FROM %s WHERE id = :id", filesTable); 217 auto stmt = db.prepare(sql); 218 stmt.get.bind(":id", id.get); 219 220 typeof(return) rval; 221 foreach (ref r; stmt.get.execute) 222 rval = Path(r.peek!string(0)); 223 return rval; 224 } 225 226 Optional!Language getFileIdLanguage(const FileId id) @trusted { 227 static immutable sql = format!"SELECT lang FROM %s WHERE id = :id"(filesTable); 228 auto stmt = db.prepare(sql); 229 stmt.get.bind(":id", id.get); 230 231 foreach (ref r; stmt.get.execute) 232 return some(r.peek!ubyte(0).to!Language); 233 return none!Language; 234 } 235 236 /// Returns: all files tagged as a root. 237 FileId[] getRootFiles() @trusted { 238 static immutable sql = format!"SELECT id FROM %s WHERE root=1"(filesTable); 239 240 auto app = appender!(FileId[])(); 241 auto stmt = db.prepare(sql); 242 foreach (ref r; stmt.get.execute) { 243 app.put(r.peek!long(0).FileId); 244 } 245 return app.data; 246 } 247 248 /// Remove the file with all mutations that are coupled to it. 249 void removeFile(const Path p) @trusted { 250 auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(filesTable)); 251 stmt.get.bind(":path", p.toString); 252 stmt.get.execute; 253 } 254 255 /// Returns: All files in the database as relative paths. 256 Path[] getFiles() @trusted { 257 auto stmt = db.prepare(format!"SELECT path FROM %s"(filesTable)); 258 auto res = stmt.get.execute; 259 260 auto app = appender!(Path[]); 261 foreach (ref r; res) { 262 app.put(Path(r.peek!string(0))); 263 } 264 265 return app.data; 266 } 267 268 Nullable!Checksum getFileChecksum(const Path p) @trusted { 269 static immutable sql = format!"SELECT checksum0,checksum1 FROM %s WHERE path=:path"( 270 filesTable); 271 auto stmt = db.prepare(sql); 272 stmt.get.bind(":path", p.toString); 273 auto res = stmt.get.execute; 274 275 typeof(return) rval; 276 if (!res.empty) { 277 rval = Checksum(res.front.peek!long(0), res.front.peek!long(1)); 278 } 279 280 return rval; 281 } 282 283 /// Returns: the timestamp of the newest file that was added. 284 Optional!SysTime getNewestFile() @trusted { 285 auto stmt = db.prepare(format!"SELECT timestamp 286 FROM %s ORDER BY datetime(timestamp) DESC LIMIT 1"( 287 filesTable)); 288 auto res = stmt.get.execute; 289 290 foreach (ref r; res) { 291 return some(r.peek!string(0).fromSqLiteDateTime); 292 } 293 294 return none!SysTime; 295 } 296 297 void put(const Path p, Checksum cs, const Language lang, bool isRoot) @trusted { 298 static immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, lang, timestamp, root) 299 VALUES (:path, :checksum0, :checksum1, :lang, :time, :root)"( 300 filesTable); 301 auto stmt = db.prepare(sql); 302 stmt.get.bind(":path", p.toString); 303 stmt.get.bind(":checksum0", cast(long) cs.c0); 304 stmt.get.bind(":checksum1", cast(long) cs.c1); 305 stmt.get.bind(":lang", cast(long) lang); 306 stmt.get.bind(":time", Clock.currTime.toSqliteDateTime); 307 stmt.get.bind(":root", isRoot); 308 stmt.get.execute; 309 } 310 311 void put(const TestFile tfile) @trusted { 312 static immutable sql = format!"INSERT OR IGNORE INTO %s (path, checksum0, checksum1, timestamp) 313 VALUES (:path, :checksum0, :checksum1, :timestamp)"( 314 testFilesTable); 315 auto stmt = db.prepare(sql); 316 stmt.get.bind(":path", tfile.file.get.toString); 317 stmt.get.bind(":checksum0", cast(long) tfile.checksum.get.c0); 318 stmt.get.bind(":checksum1", cast(long) tfile.checksum.get.c1); 319 stmt.get.bind(":timestamp", tfile.timeStamp.toSqliteDateTime); 320 stmt.get.execute; 321 } 322 323 TestFile[] getTestFiles() @trusted { 324 static immutable sql = format!"SELECT path,checksum0,checksum1,timestamp FROM %s"( 325 testFilesTable); 326 auto stmt = db.prepare(sql); 327 auto res = stmt.get.execute; 328 329 auto app = appender!(TestFile[]); 330 foreach (ref r; res) { 331 app.put(TestFile(TestFilePath(Path(r.peek!string(0))), 332 TestFileChecksum(Checksum(r.peek!long(1), r.peek!long(2))), 333 r.peek!string(3).fromSqLiteDateTime)); 334 } 335 336 return app.data; 337 } 338 339 /// Returns: the oldest test file, if it exists. 340 Optional!TestFile getNewestTestFile() @trusted { 341 auto stmt = db.prepare(format!"SELECT path,checksum0,checksum1,timestamp 342 FROM %s ORDER BY datetime(timestamp) DESC LIMIT 1"( 343 testFilesTable)); 344 auto res = stmt.get.execute; 345 346 foreach (ref r; res) { 347 return some(TestFile(TestFilePath(Path(r.peek!string(0))), 348 TestFileChecksum(Checksum(r.peek!long(1), r.peek!long(2))), 349 r.peek!string(3).fromSqLiteDateTime)); 350 } 351 352 return none!TestFile; 353 } 354 355 /// Remove the file with all mutations that are coupled to it. 356 void removeFile(const TestFilePath p) @trusted { 357 auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(testFilesTable)); 358 stmt.get.bind(":path", p.get.toString); 359 stmt.get.execute; 360 } 361 362 enum CntAction { 363 /// Increment the counter 364 incr, 365 /// Reset the counter to zero 366 reset, 367 } 368 369 /** Update the status of a mutant. 370 * 371 * Params: 372 * id = ID of the mutant 373 * st = status to broadcast 374 * d = time spent on veryfing the mutant 375 * tcs = test cases that killed the mutant 376 * counter = how to act with the counter 377 */ 378 void updateMutation(const MutationId id, const Mutation.Status st, 379 const ExitStatus ecode, const MutantTimeProfile p, const(TestCase)[] tcs) @trusted { 380 static immutable sql = "UPDATE %s SET 381 status=:st,compile_time_ms=:compile,test_time_ms=:test,update_ts=:update_ts 382 WHERE id IN (SELECT st_id FROM %s WHERE id = :id)"; 383 384 auto stmt = db.prepare(format!sql(mutationStatusTable, mutationTable)); 385 stmt.get.bind(":st", cast(long) st); 386 stmt.get.bind(":id", id.get); 387 stmt.get.bind(":compile", p.compile.total!"msecs"); 388 stmt.get.bind(":test", p.test.total!"msecs"); 389 stmt.get.bind(":update_ts", Clock.currTime.toSqliteDateTime); 390 stmt.get.execute; 391 392 updateMutationTestCases(id, tcs); 393 } 394 395 /** Update the status of a mutant. 396 * 397 * Params: 398 * id = ID of the mutant 399 * st = status to broadcast 400 * d = time spent on veryfing the mutant 401 * tcs = test cases that killed the mutant 402 * counter = how to act with the counter 403 */ 404 void updateMutation(const MutationStatusId id, const Mutation.Status st, 405 const ExitStatus ecode, const MutantTimeProfile p) @trusted { 406 static immutable sql = "UPDATE %s SET 407 status=:st,compile_time_ms=:compile,test_time_ms=:test,update_ts=:update_ts 408 WHERE id = :id"; 409 410 auto stmt = db.prepare(format!sql(mutationStatusTable)); 411 stmt.get.bind(":id", id.get); 412 stmt.get.bind(":st", cast(long) st); 413 stmt.get.bind(":compile", p.compile.total!"msecs"); 414 stmt.get.bind(":test", p.test.total!"msecs"); 415 stmt.get.bind(":update_ts", Clock.currTime.toSqliteDateTime); 416 stmt.get.execute; 417 } 418 419 /// Update the time used to test the mutant. 420 void updateMutation(const MutationStatusId id, const MutantTimeProfile p) @trusted { 421 static immutable sql = format!"UPDATE %s SET compile_time_ms=:compile,test_time_ms=:test WHERE id = :id"( 422 mutationStatusTable); 423 auto stmt = db.prepare(sql); 424 stmt.get.bind(":id", id.get); 425 stmt.get.bind(":compile", p.compile.total!"msecs"); 426 stmt.get.bind(":test", p.test.total!"msecs"); 427 stmt.get.execute; 428 } 429 430 /** Update the status of a mutant. 431 * 432 * Params: 433 * id = mutation status ID 434 * st = new status 435 * update_ts = if the update timestamp should be updated. 436 */ 437 void updateMutationStatus(const MutationStatusId id, const Mutation.Status st, 438 const ExitStatus ecode, Flag!"updateTs" update_ts = No.updateTs) @trusted { 439 440 auto stmt = () { 441 if (update_ts) { 442 const ts = Clock.currTime.toSqliteDateTime; 443 auto s = db.prepare(format!"UPDATE %s SET status=:st,update_ts=:update_ts WHERE id=:id"( 444 mutationStatusTable)); 445 s.get.bind(":update_ts", ts); 446 return s; 447 } else 448 return db.prepare(format!"UPDATE %s SET status=:st WHERE id=:id"( 449 mutationStatusTable)); 450 }(); 451 stmt.get.bind(":st", st.to!long); 452 stmt.get.bind(":id", id.to!long); 453 stmt.get.execute; 454 } 455 456 /// Returns: all mutation status IDs. 457 MutationStatusId[] getAllMutationStatus() @trusted { 458 static immutable sql = format!"SELECT id FROM %s"(mutationStatusTable); 459 460 auto app = appender!(MutationStatusId[])(); 461 auto stmt = db.prepare(sql); 462 foreach (r; stmt.get.execute) 463 app.put(MutationStatusId(r.peek!long(0))); 464 return app.data; 465 } 466 467 // TODO: change to my.optional 468 Nullable!(Mutation.Status) getMutationStatus(const MutationStatusId id) @trusted { 469 static immutable sql = format!"SELECT status FROM %s WHERE id=:id"(mutationStatusTable); 470 auto stmt = db.prepare(sql); 471 stmt.get.bind(":id", id.get); 472 473 typeof(return) rval; 474 foreach (a; stmt.get.execute) { 475 rval = cast(Mutation.Status) a.peek!long(0); 476 } 477 return rval; 478 } 479 480 Nullable!MutationEntry getMutation(const MutationId id) @trusted { 481 import dextool.plugin.mutate.backend.type; 482 483 typeof(return) rval; 484 485 static immutable get_mut_sql = format("SELECT 486 t0.id, 487 t0.kind, 488 t3.compile_time_ms, 489 t3.test_time_ms, 490 t1.offset_begin, 491 t1.offset_end, 492 t1.line, 493 t1.column, 494 t2.path, 495 t2.lang 496 FROM %s t0,%s t1,%s t2,%s t3 497 WHERE 498 t0.id = :id AND 499 t0.mp_id = t1.id AND 500 t1.file_id = t2.id AND 501 t3.id = t0.st_id 502 ", mutationTable, 503 mutationPointTable, filesTable, mutationStatusTable); 504 505 auto stmt = db.prepare(get_mut_sql); 506 stmt.get.bind(":id", cast(long) id); 507 auto res = stmt.get.execute; 508 509 if (res.empty) 510 return rval; 511 512 auto v = res.front; 513 514 auto mp = MutationPoint(Offset(v.peek!uint(4), v.peek!uint(5))); 515 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 516 auto pkey = MutationId(v.peek!long(0)); 517 auto file = Path(v.peek!string(8)); 518 auto sloc = SourceLoc(v.peek!uint(6), v.peek!uint(7)); 519 auto lang = v.peek!long(9).to!Language; 520 521 rval = MutationEntry(pkey, file, sloc, mp, 522 MutantTimeProfile(v.peek!long(2).dur!"msecs", v.peek!long(3).dur!"msecs"), lang); 523 524 return rval; 525 } 526 527 MutantMetaData getMutantationMetaData(const MutationId id) @trusted { 528 auto rval = MutantMetaData(id); 529 foreach (res; db.run(select!NomutDataTbl.where("mut_id = :mutid", Bind("mutid")), id.get)) { 530 rval.set(NoMut(res.tag, res.comment)); 531 } 532 return rval; 533 } 534 535 // TODO: fix spelling error 536 // TODO: this is a bit inefficient. it should use a callback iterator 537 MutantMetaData[] getMutantationMetaData(const Mutation.Kind[] kinds, const Mutation 538 .Status status) @trusted { 539 const sql = format!"SELECT DISTINCT t.mut_id, t.tag, t.comment 540 FROM %s t, %s t1, %s t2 541 WHERE 542 t.mut_id = t1.id AND 543 t1.st_id = t2.id AND 544 t2.status = :status AND 545 t1.kind IN (%(%s,%)) 546 ORDER BY 547 t.mut_id"(nomutDataTable, mutationTable, 548 mutationStatusTable, kinds.map!(a => cast(long) a)); 549 auto stmt = db.prepare(sql); 550 stmt.get.bind(":status", cast(long) status); 551 552 auto app = appender!(MutantMetaData[])(); 553 foreach (res; stmt.get.execute) { 554 app.put(MutantMetaData(MutationId(res.peek!long(0)), 555 MutantAttr(NoMut(res.peek!string(1), res.peek!string(2))))); 556 } 557 return app.data; 558 } 559 560 Nullable!Path getPath(const MutationId id) @trusted { 561 static immutable get_path_sql = format("SELECT t2.path 562 FROM 563 %s t0, %s t1, %s t2 564 WHERE 565 t0.id = :id AND 566 t0.mp_id = t1.id AND 567 t1.file_id = t2.id 568 ", mutationTable, 569 mutationPointTable, filesTable); 570 571 auto stmt = db.prepare(get_path_sql); 572 stmt.get.bind(":id", cast(long) id); 573 auto res = stmt.get.execute; 574 575 typeof(return) rval; 576 if (!res.empty) 577 rval = Path(res.front.peek!string(0)); 578 return rval; 579 } 580 581 /// Returns: the mutants that are connected to the mutation statuses. 582 MutantInfo[] getMutantsInfo(const Mutation.Kind[] kinds, const(MutationStatusId)[] id) @trusted { 583 const get_mutid_sql = format("SELECT t0.id,t2.status,t2.exit_code,t0.kind,t1.line,t1.column 584 FROM %s t0,%s t1, %s t2 585 WHERE 586 t0.st_id IN (%(%s,%)) AND 587 t0.st_id = t2.id AND 588 t0.kind IN (%(%s,%)) AND 589 t0.mp_id = t1.id", mutationTable, 590 mutationPointTable, mutationStatusTable, id.map!(a => a.get), 591 kinds.map!(a => cast(int) a)); 592 auto stmt = db.prepare(get_mutid_sql); 593 594 auto app = appender!(MutantInfo[])(); 595 foreach (res; stmt.get.execute) { 596 app.put(MutantInfo(MutationId(res.peek!long(0)), res.peek!long(1) 597 .to!(Mutation.Status), res.peek!int(2).ExitStatus, 598 res.peek!long(3).to!(Mutation.Kind), 599 SourceLoc(res.peek!uint(4), res.peek!uint(5)))); 600 } 601 602 return app.data; 603 } 604 605 /// Returns: the mutants that are connected to the mutation statuses. 606 MutationId[] getMutationIds(const(Mutation.Kind)[] kinds, const(MutationStatusId)[] id) @trusted { 607 if (id.length == 0) 608 return null; 609 610 const get_mutid_sql = format!"SELECT id FROM %s t0 611 WHERE 612 t0.st_id IN (%(%s,%)) AND 613 t0.kind IN (%(%s,%))"(mutationTable, 614 id.map!(a => cast(long) a), kinds.map!(a => cast(int) a)); 615 auto stmt = db.prepare(get_mutid_sql); 616 617 auto app = appender!(MutationId[])(); 618 foreach (res; stmt.get.execute) 619 app.put(MutationId(res.peek!long(0))); 620 return app.data; 621 } 622 623 Nullable!MutationId getMutationId(const MutationStatusId id) @trusted { 624 static immutable sql = format!"SELECT id FROM %s WHERE st_id=:st_id"(mutationTable); 625 auto stmt = db.prepare(sql); 626 stmt.get.bind(":st_id", id.get); 627 628 typeof(return) rval; 629 foreach (res; stmt.get.execute) { 630 rval = res.peek!long(0).MutationId; 631 break; 632 } 633 return rval; 634 } 635 636 Nullable!MutationStatusId getMutationStatusId(const MutationId id) @trusted { 637 static immutable sql = format!"SELECT st_id FROM %s WHERE id=:id"(mutationTable); 638 auto stmt = db.prepare(sql); 639 stmt.get.bind(":id", cast(long) id); 640 641 typeof(return) rval; 642 foreach (res; stmt.get.execute) { 643 rval = MutationStatusId(res.peek!long(0)); 644 } 645 return rval; 646 } 647 648 Nullable!MutationStatusId getMutationStatusId(const Checksum cs) @trusted { 649 static immutable sql = format!"SELECT id FROM %s WHERE checksum0=:cs0 AND checksum1=:cs1"( 650 mutationStatusTable); 651 auto stmt = db.prepare(sql); 652 stmt.get.bind(":cs0", cast(long) cs.c0); 653 stmt.get.bind(":cs1", cast(long) cs.c1); 654 655 typeof(return) rval; 656 foreach (res; stmt.get.execute) { 657 rval = MutationStatusId(res.peek!long(0)); 658 } 659 return rval; 660 } 661 662 // TODO: maybe this need to return the exit code too? 663 // Returns: the status of the mutant 664 Nullable!(Mutation.Status) getMutationStatus(const MutationId id) @trusted { 665 auto s = format!"SELECT status FROM %s WHERE id IN (SELECT st_id FROM %s WHERE id=:mut_id)"( 666 mutationStatusTable, mutationTable); 667 auto stmt = db.prepare(s); 668 stmt.get.bind(":mut_id", cast(long) id); 669 typeof(return) rval; 670 foreach (res; stmt.get.execute) 671 rval = res.peek!long(0).to!(Mutation.Status); 672 return rval; 673 } 674 675 /// Returns: the mutants in the file at the line. 676 MutationStatusId[] getMutationsOnLine(const(Mutation.Kind)[] kinds, FileId fid, SourceLoc sloc) @trusted { 677 // TODO: should it also be line_end? 678 const sql = format("SELECT DISTINCT t0.id FROM %s t0, %s t1, %s t2 679 WHERE 680 t1.st_id = t0.id AND 681 t1.kind IN (%(%s,%)) AND 682 t1.mp_id = t2.id AND 683 t2.file_id = :fid AND 684 (:line BETWEEN t2.line AND t2.line_end)", 685 mutationStatusTable, mutationTable, mutationPointTable, 686 kinds.map!(a => cast(int) a)); 687 auto stmt = db.prepare(sql); 688 stmt.get.bind(":fid", cast(long) fid); 689 stmt.get.bind(":line", sloc.line); 690 691 auto app = appender!(typeof(return))(); 692 foreach (res; stmt.get.execute) 693 app.put(MutationStatusId(res.peek!long(0))); 694 return app.data; 695 } 696 697 LineMetadata getLineMetadata(const FileId fid, const SourceLoc sloc) @trusted { 698 // TODO: change this select to using microrm 699 static immutable sql = format("SELECT nomut,tag,comment FROM %s 700 WHERE 701 file_id = :fid AND 702 line = :line", rawSrcMetadataTable); 703 auto stmt = db.prepare(sql); 704 stmt.get.bind(":fid", cast(long) fid); 705 stmt.get.bind(":line", sloc.line); 706 707 auto rval = typeof(return)(fid, sloc.line); 708 foreach (res; stmt.get.execute) { 709 if (res.peek!long(0) != 0) 710 rval.set(NoMut(res.peek!string(1), res.peek!string(2))); 711 } 712 713 return rval; 714 } 715 716 /// Returns: the `nr` mutants that where the longst since they where tested. 717 MutationStatusTime[] getOldestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted { 718 const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1 719 WHERE 720 t0.update_ts IS NOT NULL AND 721 t1.st_id = t0.id AND 722 t1.kind IN (%(%s,%)) 723 ORDER BY datetime(t0.update_ts) ASC LIMIT :limit", 724 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 725 auto stmt = db.prepare(sql); 726 stmt.get.bind(":limit", nr); 727 728 auto app = appender!(MutationStatusTime[])(); 729 foreach (res; stmt.get.execute) 730 app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)), 731 res.peek!string(1).fromSqLiteDateTime)); 732 return app.data; 733 } 734 735 /// Returns: the `nr` mutants that where last tested. 736 MutationStatusTime[] getLatestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted { 737 const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1 738 WHERE 739 t0.update_ts IS NOT NULL AND 740 t1.st_id = t0.id AND 741 t1.kind IN (%(%s,%)) 742 ORDER BY datetime(t0.update_ts) DESC LIMIT :limit", 743 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 744 auto stmt = db.prepare(sql); 745 stmt.get.bind(":limit", nr); 746 747 auto app = appender!(MutationStatusTime[])(); 748 foreach (res; stmt.get.execute) 749 app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)), 750 res.peek!string(1).fromSqLiteDateTime)); 751 return app.data; 752 } 753 754 /// Returns: the `nr` mutant with the highest count that has not been killed and existed in the system the longest. 755 MutationStatus[] getHighestPrioMutant(const(Mutation.Kind)[] kinds, 756 const Mutation.Status status, const long nr) @trusted { 757 const sql = format("SELECT t0.id,t0.status,t0.prio,t0.update_ts,t0.added_ts 758 FROM %s t0, %s t1 759 WHERE 760 t0.update_ts IS NOT NULL AND 761 t0.status = :status AND 762 t1.st_id = t0.id AND 763 t1.kind IN (%(%s,%)) AND 764 t1.st_id NOT IN (SELECT st_id FROM %s WHERE nomut != 0) 765 ORDER BY t0.prio DESC LIMIT :limit", 766 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a), srcMetadataTable); 767 auto stmt = db.prepare(sql); 768 stmt.get.bind(":status", cast(long) status); 769 stmt.get.bind(":limit", nr); 770 771 auto app = appender!(MutationStatus[])(); 772 foreach (res; stmt.get.execute) { 773 auto added = () { 774 auto raw = res.peek!string(4); 775 if (raw.length == 0) 776 return Nullable!SysTime(); 777 return Nullable!SysTime(raw.fromSqLiteDateTime); 778 }(); 779 780 // dfmt off 781 app.put(MutationStatus( 782 MutationStatusId(res.peek!long(0)), 783 res.peek!long(1).to!(Mutation.Status), 784 res.peek!long(2).MutantPrio, 785 res.peek!string(3).fromSqLiteDateTime, 786 added, 787 )); 788 // dfmt on 789 } 790 791 return app.data; 792 } 793 794 /** Get SourceLoc for a specific mutation id. 795 */ 796 Nullable!SourceLoc getSourceLocation(MutationId id) @trusted { 797 auto s = format!"SELECT line, column FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE id=:mut_id)"( 798 mutationPointTable, mutationTable); 799 auto stmt = db.prepare(s); 800 stmt.get.bind(":mut_id", cast(long) id); 801 typeof(return) rval; 802 foreach (res; stmt.get.execute) 803 rval = SourceLoc(res.peek!uint(0), res.peek!uint(1)); 804 return rval; 805 } 806 807 /** Remove all mutations of kinds. 808 */ 809 void removeMutant(const Mutation.Kind[] kinds) @trusted { 810 const s = format!"DELETE FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE kind IN (%(%s,%)))"( 811 mutationPointTable, mutationTable, kinds.map!(a => cast(int) a)); 812 auto stmt = db.prepare(s); 813 stmt.get.execute; 814 } 815 816 /** Reset all mutations of kinds with the status `st` to unknown. 817 */ 818 void resetMutant(const Mutation.Kind[] kinds, Mutation.Status st, Mutation.Status to_st) @trusted { 819 const s = format!"UPDATE %s SET status=%s WHERE status = %s AND id IN(SELECT st_id FROM %s WHERE kind IN (%(%s,%)))"( 820 mutationStatusTable, to_st.to!long, st.to!long, 821 mutationTable, kinds.map!(a => cast(int) a)); 822 auto stmt = db.prepare(s); 823 stmt.get.execute; 824 } 825 826 /** Mark a mutant with status and rationale (also adds metadata). 827 */ 828 void markMutant(const MutationId id, const Path file, const SourceLoc sloc, 829 const MutationStatusId statusId, const Checksum cs, 830 const Mutation.Status s, const Rationale r, string mutationTxt) @trusted { 831 db.run(insertOrReplace!MarkedMutantTbl, MarkedMutantTbl(cs.c0, cs.c1, 832 statusId.get, id.get, sloc.line, sloc.column, file, s, 833 Clock.currTime.toUTC, r.get, mutationTxt)); 834 } 835 836 void removeMarkedMutant(const Checksum cs) @trusted { 837 db.run(delete_!MarkedMutantTbl.where("checksum0 = :cs0", Bind("cs0")), cast(long) cs.c0); 838 } 839 840 void removeMarkedMutant(const MutationStatusId id) @trusted { 841 db.run(delete_!MarkedMutantTbl.where("st_id = :st_id", Bind("st_id")), id.get); 842 } 843 844 /// Returns: All mutants with that are marked orderd by their path 845 MarkedMutant[] getMarkedMutants() @trusted { 846 import miniorm : OrderingTermSort; 847 848 auto app = appender!(MarkedMutant[])(); 849 foreach (m; db.run(select!MarkedMutantTbl.orderBy(OrderingTermSort.ASC, [ 850 "path" 851 ]))) { 852 app.put(.make(m)); 853 } 854 855 return app.data; 856 } 857 858 Mutation.Kind getKind(MutationId id) @trusted { 859 static immutable sql = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable); 860 auto stmt = db.prepare(sql); 861 stmt.get.bind(":id", cast(long) id); 862 863 typeof(return) rval; 864 foreach (res; stmt.get.execute) { 865 rval = res.peek!long(0).to!(Mutation.Kind); 866 } 867 return rval; 868 } 869 870 import dextool.plugin.mutate.backend.type; 871 872 alias aliveSrcMutants = countMutants!([ 873 Mutation.Status.alive, Mutation.Status.noCoverage 874 ]); 875 alias killedSrcMutants = countMutants!([Mutation.Status.killed]); 876 alias timeoutSrcMutants = countMutants!([Mutation.Status.timeout]); 877 alias noCovSrcMutants = countMutants!([Mutation.Status.noCoverage]); 878 alias equivalentMutants = countMutants!([Mutation.Status.equivalent]); 879 880 /// Returns: Total that should be counted when calculating the mutation score. 881 alias totalSrcMutants = countMutants!([ 882 Mutation.Status.alive, Mutation.Status.killed, 883 Mutation.Status.timeout, Mutation.Status.noCoverage 884 ]); 885 886 alias unknownSrcMutants = countMutants!([Mutation.Status.unknown]); 887 alias killedByCompilerSrcMutants = countMutants!([ 888 Mutation.Status.killedByCompiler 889 ]); 890 891 /** Count the mutants with the nomut metadata. 892 * 893 * Params: 894 * status = status the mutants must be in to be counted. 895 * distinc = count based on unique source code changes. 896 * kinds = the kind of mutants to count. 897 * file = file to count mutants in. 898 */ 899 private MutationReportEntry countMutants(int[] status)(const Mutation.Kind[] kinds, 900 string file = null) @trusted { 901 const qq = " 902 SELECT count(*),sum(compile_time_ms),sum(test_time_ms) 903 FROM ( 904 SELECT sum(t1.compile_time_ms) compile_time_ms,sum(t1.test_time_ms) test_time_ms 905 FROM %s t0, %s t1%s 906 WHERE 907 %s 908 t0.st_id = t1.id AND 909 t1.status IN (%(%s,%)) AND 910 t0.kind IN (%(%s,%)) 911 GROUP BY t1.id)"; 912 const query = () { 913 auto fq = file.length == 0 914 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 915 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 916 mutationPointTable, filesTable); 917 return format(qq, mutationTable, mutationStatusTable, fq_from, fq, 918 status, kinds.map!(a => cast(int) a)); 919 }(); 920 921 typeof(return) rval; 922 auto stmt = db.prepare(query); 923 if (file.length != 0) 924 stmt.get.bind(":path", file); 925 auto res = stmt.get.execute; 926 if (!res.empty) { 927 rval = MutationReportEntry(res.front.peek!long(0), 928 MutantTimeProfile(res.front.peek!long(1).dur!"msecs", 929 res.front.peek!long(2).dur!"msecs")); 930 } 931 return rval; 932 } 933 934 /** Count the mutants with the nomut metadata. 935 * 936 * Params: 937 * status = status the mutants must be in to be counted. 938 * distinc = count based on unique source code changes. 939 * kinds = the kind of mutants to count. 940 * file = file to count mutants in. 941 */ 942 private MetadataNoMutEntry countNoMutMutants(int[] status, bool distinct)( 943 const Mutation.Kind[] kinds, string file = null) @trusted { 944 static if (distinct) { 945 auto sql_base = " 946 SELECT count(*) 947 FROM ( 948 SELECT count(*) 949 FROM %s t0, %s t1,%s t4%s 950 WHERE 951 %s 952 t0.st_id = t1.id AND 953 t0.st_id = t4.st_id AND 954 t4.nomut != 0 AND 955 t1.status IN (%(%s,%)) AND 956 t0.kind IN (%(%s,%)) 957 GROUP BY t1.id)"; 958 } else { 959 auto sql_base = " 960 SELECT count(*) 961 FROM %s t0, %s t1,%s t4%s 962 WHERE 963 %s 964 t0.st_id = t1.id AND 965 t0.st_id = t4.st_id AND 966 t4.nomut != 0 AND 967 t1.status IN (%(%s,%)) AND 968 t0.kind IN (%(%s,%))"; 969 } 970 const query = () { 971 auto fq = file.length == 0 972 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 973 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 974 mutationPointTable, filesTable); 975 return format(sql_base, mutationTable, mutationStatusTable, 976 srcMetadataTable, fq_from, fq, status, kinds.map!(a => cast(int) a)); 977 }(); 978 979 typeof(return) rval; 980 auto stmt = db.prepare(query); 981 if (file.length != 0) 982 stmt.get.bind(":path", file); 983 auto res = stmt.get.execute; 984 if (!res.empty) 985 rval = MetadataNoMutEntry(res.front.peek!long(0)); 986 return rval; 987 } 988 989 /// ditto. 990 alias aliveNoMutSrcMutants = countNoMutMutants!([Mutation.Status.alive], true); 991 992 /// Returns: mutants killed by the test case. 993 MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, TestCase tc) @trusted { 994 const query = format(" 995 SELECT t1.id 996 FROM %s t0, %s t1, %s t2, %s t3 997 WHERE 998 t0.st_id = t1.id AND 999 t1.status = :st AND 1000 t0.kind IN (%(%s,%)) AND 1001 t2.name = :name AND 1002 t2.id = t3.tc_id AND 1003 t3.st_id = t1.id 1004 GROUP BY t1.id", mutationTable, mutationStatusTable, 1005 allTestCaseTable, killedTestCaseTable, kinds.map!(a => cast(int) a)); 1006 1007 auto stmt = db.prepare(query); 1008 stmt.get.bind(":st", cast(long) Mutation.Status.killed); 1009 stmt.get.bind(":name", tc.name); 1010 1011 auto app = appender!(MutationStatusId[])(); 1012 foreach (res; stmt.get.execute) 1013 app.put(MutationStatusId(res.peek!long(0))); 1014 1015 return app.data; 1016 } 1017 1018 /// Returns: mutants at mutations points that the test case has killed mutants at. 1019 alias testCaseMutationPointAliveSrcMutants = testCaseCountSrcMutants!([ 1020 Mutation.Status.alive 1021 ]); 1022 /// ditto 1023 alias testCaseMutationPointTimeoutSrcMutants = testCaseCountSrcMutants!( 1024 [Mutation.Status.timeout]); 1025 /// ditto 1026 alias testCaseMutationPointKilledSrcMutants = testCaseCountSrcMutants!([ 1027 Mutation.Status.killed 1028 ]); 1029 /// ditto 1030 alias testCaseMutationPointUnknownSrcMutants = testCaseCountSrcMutants!( 1031 [Mutation.Status.unknown]); 1032 /// ditto 1033 alias testCaseMutationPointKilledByCompilerSrcMutants = testCaseCountSrcMutants!( 1034 [Mutation.Status.killedByCompiler]); 1035 /// ditto 1036 alias testCaseMutationPointTotalSrcMutants = testCaseCountSrcMutants!( 1037 [ 1038 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 1039 ]); 1040 1041 private MutationStatusId[] testCaseCountSrcMutants(int[] status)( 1042 const Mutation.Kind[] kinds, TestCase tc) @trusted { 1043 const query = format(" 1044 SELECT t1.id 1045 FROM %s t0, %s t1 1046 WHERE 1047 t0.mp_id IN (SELECT t1.id 1048 FROM %s t0,%s t1, %s t2, %s t3 1049 WHERE 1050 t0.mp_id = t1.id AND 1051 t2.name = :name AND 1052 t2.id = t3.tc_id AND 1053 t3.st_id = t0.st_id 1054 ) 1055 AND 1056 t0.st_id = t1.id AND 1057 t1.status IN (%(%s,%)) AND 1058 t0.kind IN (%(%s,%)) 1059 GROUP BY t1.id", mutationTable, mutationStatusTable, mutationTable, mutationPointTable, 1060 allTestCaseTable, killedTestCaseTable, status, kinds.map!(a => cast(int) a)); 1061 1062 auto stmt = db.prepare(query); 1063 stmt.get.bind(":name", tc.name); 1064 1065 auto app = appender!(MutationStatusId[])(); 1066 foreach (res; stmt.get.execute) 1067 app.put(MutationStatusId(res.peek!long(0))); 1068 1069 return app.data; 1070 } 1071 1072 Nullable!Checksum getChecksum(MutationStatusId id) @trusted { 1073 static immutable sql = format!"SELECT checksum0, checksum1 FROM %s WHERE id=:id"( 1074 mutationStatusTable); 1075 auto stmt = db.prepare(sql); 1076 stmt.get.bind(":id", id.get); 1077 1078 typeof(return) rval; 1079 foreach (res; stmt.get.execute) { 1080 rval = Checksum(res.peek!long(0), res.peek!long(1)); 1081 break; 1082 } 1083 return rval; 1084 } 1085 1086 /// Remove all metadata. 1087 void clearMetadata() { 1088 static immutable sql = format!"DELETE FROM %s"(rawSrcMetadataTable); 1089 db.run(sql); 1090 } 1091 1092 /** Save line metadata to the database which is used to associate line 1093 * metadata with mutants. 1094 */ 1095 void put(const LineMetadata[] mdata) { 1096 import sumtype; 1097 1098 if (mdata.empty) 1099 return; 1100 1101 // TODO: convert to microrm 1102 static immutable sql = format!"INSERT OR IGNORE INTO %s 1103 (file_id, line, nomut, tag, comment) 1104 VALUES(:fid, :line, :nomut, :tag, :comment)"( 1105 rawSrcMetadataTable); 1106 1107 auto stmt = db.prepare(sql); 1108 foreach (meta; mdata) { 1109 auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a); 1110 stmt.get.bindAll(cast(long) meta.id, meta.line, meta.isNoMut, 1111 nomut.tag, nomut.comment); 1112 stmt.get.execute; 1113 stmt.get.reset; 1114 } 1115 } 1116 1117 /// Store all found mutants. 1118 void put(MutationPointEntry2[] mps, AbsolutePath root) @trusted { 1119 if (mps.empty) 1120 return; 1121 1122 static immutable insert_mp_sql = format("INSERT OR IGNORE INTO %s 1123 (file_id, offset_begin, offset_end, line, column, line_end, column_end) 1124 SELECT id,:begin,:end,:line,:column,:line_end,:column_end 1125 FROM %s 1126 WHERE 1127 path = :path", 1128 mutationPointTable, filesTable); 1129 auto mp_stmt = db.prepare(insert_mp_sql); 1130 1131 foreach (mp; mps) { 1132 auto rel_file = relativePath(mp.file, root).Path; 1133 mp_stmt.get.bind(":begin", mp.offset.begin); 1134 mp_stmt.get.bind(":end", mp.offset.end); 1135 mp_stmt.get.bind(":line", mp.sloc.line); 1136 mp_stmt.get.bind(":column", mp.sloc.column); 1137 mp_stmt.get.bind(":line_end", mp.slocEnd.line); 1138 mp_stmt.get.bind(":column_end", mp.slocEnd.column); 1139 mp_stmt.get.bind(":path", cast(string) rel_file); 1140 mp_stmt.get.execute; 1141 mp_stmt.get.reset; 1142 } 1143 1144 static immutable insert_cmut_sql = format("INSERT OR IGNORE INTO %s 1145 (status,exit_code,compile_time_ms,test_time_ms,update_ts,added_ts,checksum0,checksum1,prio) 1146 VALUES(:st,0,0,0,:update_ts,:added_ts,:c0,:c1,:prio)", 1147 mutationStatusTable); 1148 auto cmut_stmt = db.prepare(insert_cmut_sql); 1149 const ts = Clock.currTime.toSqliteDateTime; 1150 cmut_stmt.get.bind(":st", Mutation.Status.unknown); 1151 cmut_stmt.get.bind(":update_ts", ts); 1152 cmut_stmt.get.bind(":added_ts", ts); 1153 foreach (mp; mps) { 1154 const prio = (mp.offset.begin < mp.offset.end) ? mp.offset.end - mp.offset.begin : 0; 1155 foreach (cm; mp.cms) { 1156 cmut_stmt.get.bind(":c0", cast(long) cm.id.c0); 1157 cmut_stmt.get.bind(":c1", cast(long) cm.id.c1); 1158 cmut_stmt.get.bind(":prio", prio); 1159 cmut_stmt.get.execute; 1160 cmut_stmt.get.reset; 1161 } 1162 } 1163 1164 static immutable insert_m_sql = format("INSERT OR IGNORE INTO %s 1165 (mp_id, st_id, kind) 1166 SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE 1167 t2.path = :path AND 1168 t0.file_id = t2.id AND 1169 t0.offset_begin = :off_begin AND 1170 t0.offset_end = :off_end AND 1171 t1.checksum0 = :c0 AND 1172 t1.checksum1 = :c1", 1173 mutationTable, mutationPointTable, mutationStatusTable, filesTable); 1174 auto insert_m = db.prepare(insert_m_sql); 1175 1176 foreach (mp; mps) { 1177 foreach (m; mp.cms) { 1178 auto rel_file = relativePath(mp.file, root).Path; 1179 insert_m.get.bind(":path", cast(string) rel_file); 1180 insert_m.get.bind(":off_begin", mp.offset.begin); 1181 insert_m.get.bind(":off_end", mp.offset.end); 1182 insert_m.get.bind(":c0", cast(long) m.id.c0); 1183 insert_m.get.bind(":c1", cast(long) m.id.c1); 1184 insert_m.get.bind(":kind", m.mut.kind); 1185 insert_m.get.execute; 1186 insert_m.get.reset; 1187 } 1188 } 1189 } 1190 1191 /** Remove all mutants points from the database. 1192 * 1193 * This removes all the mutants because of the cascade delete of the 1194 * tables. But it will keep the mutation statuses and thus the checksums 1195 * and the status of the code changes. 1196 * 1197 * This then mean that when mutations+mutation points are added back they 1198 * may reconnect with a mutation status. 1199 */ 1200 void removeAllMutationPoints() @trusted { 1201 static immutable sql = format!"DELETE FROM %s"(mutationPointTable); 1202 db.run(sql); 1203 } 1204 1205 /// ditto 1206 void removeAllFiles() @trusted { 1207 static immutable sql = format!"DELETE FROM %s"(filesTable); 1208 db.run(sql); 1209 } 1210 1211 /// Remove mutants that have no connection to a mutation point, orphaned mutants. 1212 void removeOrphanedMutants() @trusted { 1213 import std.datetime.stopwatch : StopWatch, AutoStart; 1214 1215 const removeIds = () { 1216 static immutable sql = format!"SELECT id FROM %1$s WHERE id NOT IN (SELECT st_id FROM %2$s)"( 1217 mutationStatusTable, mutationTable); 1218 auto stmt = db.prepare(sql); 1219 auto removeIds = appender!(long[])(); 1220 foreach (res; stmt.get.execute) 1221 removeIds.put(res.peek!long(0)); 1222 return removeIds.data; 1223 }(); 1224 1225 immutable batchNr = 1000; 1226 static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(mutationStatusTable); 1227 auto stmt = db.prepare(sql); 1228 auto sw = StopWatch(AutoStart.yes); 1229 foreach (const i, const id; removeIds) { 1230 stmt.get.bind(":id", id); 1231 stmt.get.execute; 1232 stmt.get.reset; 1233 1234 // continuously print to inform the user of the progress and avoid 1235 // e.g. timeout on jenkins. 1236 if (i > 0 && i % batchNr == 0) { 1237 const avg = cast(long)(cast(double) sw.peek.total!"msecs" / cast(double) batchNr); 1238 const t = dur!"msecs"(avg * (removeIds.length - i)); 1239 logger.infof("%s/%s removed (average %sms) (%s) (%s)", i, 1240 removeIds.length, avg, t, (Clock.currTime + t).toSimpleString); 1241 sw.reset; 1242 } 1243 } 1244 1245 logger.infof(!removeIds.empty, "%1$s/%1$s removed", removeIds.length); 1246 } 1247 1248 /** Add a link between the mutation and what test case killed it. 1249 * 1250 * Params: 1251 * id = ? 1252 * tcs = test cases to add 1253 */ 1254 void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted { 1255 if (tcs.length == 0) 1256 return; 1257 1258 immutable statusId = () { 1259 static immutable st_id_for_mutation_q = format!"SELECT st_id FROM %s WHERE id=:id"( 1260 mutationTable); 1261 auto stmt = db.prepare(st_id_for_mutation_q); 1262 stmt.get.bind(":id", cast(long) id); 1263 return stmt.get.execute.oneValue!long; 1264 }(); 1265 updateMutationTestCases(MutationStatusId(statusId), tcs); 1266 } 1267 1268 /** Add a link between the mutation and what test case killed it. 1269 * 1270 * Params: 1271 * id = ? 1272 * tcs = test cases to add 1273 */ 1274 void updateMutationTestCases(const MutationStatusId statusId, const(TestCase)[] tcs) @trusted { 1275 if (tcs.length == 0) 1276 return; 1277 1278 try { 1279 static immutable remove_old_sql = format!"DELETE FROM %s WHERE st_id=:id"( 1280 killedTestCaseTable); 1281 auto stmt = db.prepare(remove_old_sql); 1282 stmt.get.bind(":id", statusId.get); 1283 stmt.get.execute; 1284 } catch (Exception e) { 1285 } 1286 1287 static immutable add_if_non_exist_tc_sql = format!"INSERT OR IGNORE INTO %s (name) SELECT :name1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)"( 1288 allTestCaseTable, allTestCaseTable); 1289 auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql); 1290 1291 static immutable add_new_sql = format!"INSERT OR IGNORE INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc"( 1292 killedTestCaseTable, allTestCaseTable); 1293 auto stmt_insert = db.prepare(add_new_sql); 1294 foreach (const tc; tcs) { 1295 try { 1296 stmt_insert_tc.get.reset; 1297 stmt_insert_tc.get.bind(":name1", tc.name); 1298 stmt_insert_tc.get.bind(":name2", tc.name); 1299 stmt_insert_tc.get.execute; 1300 1301 stmt_insert.get.reset; 1302 stmt_insert.get.bind(":st_id", statusId.get); 1303 stmt_insert.get.bind(":loc", tc.location); 1304 stmt_insert.get.bind(":tc", tc.name); 1305 stmt_insert.get.execute; 1306 } catch (Exception e) { 1307 logger.warning(e.msg); 1308 } 1309 } 1310 } 1311 1312 /** Set detected test cases. 1313 * 1314 * This will replace those that where previously stored. 1315 * 1316 * Returns: ID of affected mutation statuses. 1317 */ 1318 MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted { 1319 if (tcs.length == 0) 1320 return null; 1321 1322 auto ids = appender!(MutationStatusId[])(); 1323 1324 static immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 1325 internalAddDetectedTestCases(tcs, tmp_name); 1326 1327 static immutable mut_st_id = format!"SELECT DISTINCT t1.st_id 1328 FROM %s t0, %s t1 1329 WHERE 1330 t0.name NOT IN (SELECT name FROM %s) AND 1331 t0.id = t1.tc_id"(allTestCaseTable, 1332 killedTestCaseTable, tmp_name); 1333 auto stmt = db.prepare(mut_st_id); 1334 foreach (res; stmt.get.execute) { 1335 ids.put(res.peek!long(0).MutationStatusId); 1336 } 1337 1338 static immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"( 1339 allTestCaseTable, tmp_name); 1340 db.run(remove_old_sql); 1341 1342 db.run(format!"DROP TABLE %s"(tmp_name)); 1343 1344 return ids.data; 1345 } 1346 1347 /** Add test cases to those that have been detected. 1348 * 1349 * They will be added if they are unique. 1350 */ 1351 void addDetectedTestCases(const(TestCase)[] tcs) @trusted { 1352 if (tcs.length == 0) 1353 return; 1354 1355 static immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 1356 internalAddDetectedTestCases(tcs, tmp_name); 1357 db.run(format!"DROP TABLE %s"(tmp_name)); 1358 } 1359 1360 /// ditto. 1361 private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted { 1362 db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"( 1363 tmp_tbl)); 1364 1365 const add_tc_sql = format!"INSERT OR IGNORE INTO %s (name) VALUES(:name)"(tmp_tbl); 1366 auto insert_s = db.prepare(add_tc_sql); 1367 foreach (tc; tcs.filter!(a => !a.name.empty)) { 1368 insert_s.get.bind(":name", tc.name); 1369 insert_s.get.execute; 1370 insert_s.get.reset; 1371 } 1372 1373 // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table 1374 //Q: What is happening here? 1375 // 1376 //A: Conceptually, we select all rows from table1 and for each row we 1377 //attempt to find a row in table2 with the same value for the name 1378 //column. If there is no such row, we just leave the table2 portion of 1379 //our result empty for that row. Then we constrain our selection by 1380 //picking only those rows in the result where the matching row does not 1381 //exist. Finally, We ignore all fields from our result except for the 1382 //name column (the one we are sure that exists, from table1). 1383 // 1384 //While it may not be the most performant method possible in all cases, 1385 //it should work in basically every database engine ever that attempts 1386 //to implement ANSI 92 SQL 1387 const add_missing_sql = format!"INSERT OR IGNORE INTO %s (name) SELECT t1.name FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL"( 1388 allTestCaseTable, tmp_tbl, allTestCaseTable); 1389 db.run(add_missing_sql); 1390 } 1391 1392 /// Returns: detected test cases. 1393 TestCase[] getDetectedTestCases() @trusted { 1394 auto rval = appender!(TestCase[])(); 1395 db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval); 1396 return rval.data; 1397 } 1398 1399 /// Returns: detected test cases. 1400 TestCaseId[] getDetectedTestCaseIds() @trusted { 1401 auto rval = appender!(TestCaseId[])(); 1402 db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval); 1403 return rval.data; 1404 } 1405 1406 /// Returns: test cases that has killed zero mutants. 1407 TestCase[] getTestCasesWithZeroKills() @trusted { 1408 static immutable sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)", 1409 allTestCaseTable, killedTestCaseTable); 1410 1411 auto rval = appender!(TestCase[])(); 1412 auto stmt = db.prepare(sql); 1413 foreach (a; stmt.get.execute) 1414 rval.put(TestCase(a.peek!string(0))); 1415 1416 return rval.data; 1417 } 1418 1419 /** Guarantees that the there are no duplications of `TestCaseId`. 1420 * 1421 * Returns: test cases that has killed at least one mutant. 1422 */ 1423 TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted { 1424 const sql = format!"SELECT DISTINCT t1.id 1425 FROM %s t1, %s t2, %s t3 1426 WHERE 1427 t1.id = t2.tc_id AND 1428 t2.st_id == t3.st_id AND 1429 t3.kind IN (%(%s,%))"(allTestCaseTable, 1430 killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a)); 1431 1432 auto rval = appender!(TestCaseId[])(); 1433 auto stmt = db.prepare(sql); 1434 foreach (a; stmt.get.execute) 1435 rval.put(TestCaseId(a.peek!long(0))); 1436 1437 return rval.data; 1438 } 1439 1440 /// Returns: the name of the test case. 1441 string getTestCaseName(const TestCaseId id) @trusted { 1442 static immutable sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 1443 auto stmt = db.prepare(sql); 1444 stmt.get.bind(":id", cast(long) id); 1445 auto res = stmt.get.execute; 1446 return res.oneValue!string; 1447 } 1448 1449 /// Returns: stats about the test case. 1450 Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @trusted { 1451 const sql = format("SELECT sum(ctime),sum(ttime),count(*) 1452 FROM ( 1453 SELECT sum(t2.compile_time_ms) ctime,sum(t2.test_time_ms) ttime 1454 FROM %s t0, %s t1, %s t2, %s t3 1455 WHERE 1456 t0.name = :name AND 1457 t0.id = t1.tc_id AND 1458 t1.st_id = t2.id AND 1459 t1.st_id = t3.st_id AND 1460 t3.kind IN (%(%s,%)) 1461 GROUP BY t1.st_id)", allTestCaseTable, 1462 killedTestCaseTable, mutationStatusTable, mutationTable, 1463 kinds.map!(a => cast(int) a)); 1464 auto stmt = db.prepare(sql); 1465 stmt.get.bind(":name", tc.name); 1466 1467 typeof(return) rval; 1468 foreach (a; stmt.get.execute) { 1469 rval = TestCaseInfo(MutantTimeProfile(a.peek!long(0).dur!"msecs", 1470 a.peek!long(1).dur!"msecs"), a.peek!long(2)); 1471 } 1472 return rval; 1473 } 1474 1475 /// Returns: all test cases for the file and the mutants they killed. 1476 TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted { 1477 // row of test case name and mutation id. 1478 const sql = format("SELECT t0.name,t3.id 1479 FROM %s t0, %s t1, %s t2, %s t3, %s t4 1480 WHERE 1481 t0.id = t1.tc_id AND 1482 t1.st_id = t2.id AND 1483 t2.id = t3.st_id AND 1484 t4.id = :file_id AND 1485 t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable, 1486 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a)); 1487 auto stmt = db.prepare(sql); 1488 stmt.get.bind(":file_id", cast(long) file); 1489 1490 MutationId[][string] data; 1491 foreach (row; stmt.get.execute) { 1492 const name = row.peek!string(0); 1493 if (auto v = name in data) { 1494 *v ~= MutationId(row.peek!long(1)); 1495 } else { 1496 data[name] = [MutationId(row.peek!long(1))]; 1497 } 1498 } 1499 1500 auto app = appender!(TestCaseInfo2[])(); 1501 data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app); 1502 return app.data; 1503 } 1504 1505 /// Returns: the test case. 1506 Nullable!TestCase getTestCase(const TestCaseId id) @trusted { 1507 static immutable sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 1508 auto stmt = db.prepare(sql); 1509 stmt.get.bind(":id", cast(long) id); 1510 1511 typeof(return) rval; 1512 foreach (res; stmt.get.execute) { 1513 rval = TestCase(res.peek!string(0)); 1514 } 1515 return rval; 1516 } 1517 1518 /// Returns: the test case id. 1519 Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted { 1520 static immutable sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable); 1521 auto stmt = db.prepare(sql); 1522 stmt.get.bind(":name", tc.name); 1523 1524 typeof(return) rval; 1525 foreach (res; stmt.get.execute) { 1526 rval = TestCaseId(res.peek!long(0)); 1527 } 1528 return rval; 1529 } 1530 1531 /// The mutation ids are guaranteed to be sorted. 1532 /// Returns: the mutants the test case killed. 1533 MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted { 1534 immutable sql = format!"SELECT t2.id 1535 FROM %s t1, %s t2 1536 WHERE 1537 t1.tc_id = :tid AND 1538 t1.st_id = t2.st_id AND 1539 t2.kind IN (%(%s,%)) 1540 GROUP BY t2.st_id 1541 ORDER BY t2.id"(killedTestCaseTable, 1542 mutationTable, kinds.map!(a => cast(int) a)); 1543 1544 auto rval = appender!(MutationId[])(); 1545 auto stmt = db.prepare(sql); 1546 stmt.get.bind(":tid", cast(long) id); 1547 foreach (a; stmt.get.execute) 1548 rval.put(MutationId(a.peek!long(0))); 1549 1550 return rval.data; 1551 } 1552 1553 /// Returns: test cases that killed the mutant. 1554 TestCase[] getTestCases(const MutationId id) @trusted { 1555 Appender!(TestCase[]) rval; 1556 1557 static immutable get_test_cases_sql = format!"SELECT t1.name,t2.location 1558 FROM %s t1, %s t2, %s t3 1559 WHERE 1560 t3.id = :id AND 1561 t3.st_id = t2.st_id AND 1562 t2.tc_id = t1.id"( 1563 allTestCaseTable, killedTestCaseTable, mutationTable); 1564 auto stmt = db.prepare(get_test_cases_sql); 1565 stmt.get.bind(":id", cast(long) id); 1566 foreach (a; stmt.get.execute) 1567 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 1568 1569 return rval.data; 1570 } 1571 1572 /// Returns: if the mutant have any test cases recorded that killed it 1573 bool hasTestCases(const MutationStatusId id) @trusted { 1574 static immutable sql = format!"SELECT count(*) FROM %s t0 WHERE t0.st_id = :id"( 1575 killedTestCaseTable); 1576 auto stmt = db.prepare(sql); 1577 stmt.get.bind(":id", id.get); 1578 foreach (a; stmt.get.execute) { 1579 return a.peek!long(0) != 0; 1580 } 1581 return false; 1582 } 1583 1584 /** Returns: number of test cases 1585 */ 1586 long getNumOfTestCases() @trusted { 1587 static immutable num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable); 1588 return db.execute(num_test_cases_sql).oneValue!long; 1589 } 1590 1591 /** Returns: test cases that killed other mutants at the same mutation point as `id`. 1592 */ 1593 TestCase[] getSurroundingTestCases(const MutationId id) @trusted { 1594 Appender!(TestCase[]) rval; 1595 1596 // get the mutation point ID that id reside at 1597 long mp_id; 1598 { 1599 auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE id=:id"(mutationTable)); 1600 stmt.get.bind(":id", cast(long) id); 1601 auto res = stmt.get.execute; 1602 if (res.empty) 1603 return null; 1604 mp_id = res.oneValue!long; 1605 } 1606 1607 // get all the test cases that are killed at the mutation point 1608 static immutable get_test_cases_sql = format!"SELECT t2.name,t1.location FROM %s t1,%s t2 WHERE t1.tc_id == t2.id AND t0.st_id IN (SELECT st_id FROM %s WHERE mp_id=:id)"( 1609 killedTestCaseTable, allTestCaseTable, mutationTable); 1610 auto stmt = db.prepare(get_test_cases_sql); 1611 stmt.get.bind(":id", mp_id); 1612 foreach (a; stmt.get.execute) { 1613 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 1614 } 1615 1616 return rval.data; 1617 } 1618 1619 void removeTestCase(const TestCaseId id) @trusted { 1620 auto stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable)); 1621 stmt.get.bind(":id", cast(long) id); 1622 stmt.get.execute; 1623 } 1624 1625 /// Change the status of all mutants that the test case has killed to unknown. 1626 void resetTestCaseId(const TestCaseId id) @trusted { 1627 { 1628 static immutable sql = format!"UPDATE %1$s SET status=0 WHERE id IN (SELECT t1.id FROM %2$s t0, %1$s t1 WHERE t0.tc_id = :id AND t0.st_id = t1.id)"( 1629 mutationStatusTable, killedTestCaseTable); 1630 auto stmt = db.prepare(sql); 1631 stmt.get.bind(":id", cast(long) id); 1632 stmt.get.execute; 1633 } 1634 { 1635 static immutable sql2 = format!"DELETE FROM %1$s WHERE tc_id = :id"(killedTestCaseTable); 1636 auto stmt = db.prepare(sql2); 1637 stmt.get.bind(":id", cast(long) id); 1638 stmt.get.execute; 1639 } 1640 } 1641 1642 /// Returns: the context for the timeout algorithm. 1643 MutantTimeoutCtx getMutantTimeoutCtx() @trusted { 1644 foreach (res; db.run(select!MutantTimeoutCtx)) 1645 return res; 1646 return MutantTimeoutCtx.init; 1647 } 1648 1649 void putMutantTimeoutCtx(const MutantTimeoutCtx ctx) @trusted { 1650 db.run(delete_!MutantTimeoutCtx); 1651 db.run(insert!MutantTimeoutCtx.insert, ctx); 1652 } 1653 1654 void putMutantInTimeoutWorklist(const MutationStatusId id) @trusted { 1655 const sql = format!"INSERT OR IGNORE INTO %s (id) VALUES (:id)"(mutantTimeoutWorklistTable); 1656 auto stmt = db.prepare(sql); 1657 stmt.get.bind(":id", id.get); 1658 stmt.get.execute; 1659 } 1660 1661 /** Remove all mutants that are in the worklist that do NOT have the 1662 * mutation status timeout. 1663 */ 1664 void reduceMutantTimeoutWorklist() @trusted { 1665 static immutable sql = format!"DELETE FROM %1$s 1666 WHERE 1667 id IN (SELECT id FROM %2$s WHERE status != :status)"( 1668 mutantTimeoutWorklistTable, mutationStatusTable); 1669 auto stmt = db.prepare(sql); 1670 stmt.get.bind(":status", cast(ubyte) Mutation.Status.timeout); 1671 stmt.get.execute; 1672 } 1673 1674 /// Remove all mutants from the worklist. 1675 void clearMutantTimeoutWorklist() @trusted { 1676 static immutable sql = format!"DELETE FROM %1$s"(mutantTimeoutWorklistTable); 1677 db.run(sql); 1678 } 1679 1680 /// Returns: the number of mutants in the worklist. 1681 long countMutantTimeoutWorklist() @trusted { 1682 static immutable sql = format!"SELECT count(*) FROM %1$s"(mutantTimeoutWorklistTable); 1683 auto stmt = db.prepare(sql); 1684 auto res = stmt.get.execute(); 1685 return res.oneValue!long; 1686 } 1687 1688 /// Changes the status of mutants in the timeout worklist to unknown. 1689 void resetMutantTimeoutWorklist(Mutation.Status toStatus) @trusted { 1690 static immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"( 1691 mutationStatusTable, mutantTimeoutWorklistTable); 1692 auto stmt = db.prepare(sql); 1693 stmt.get.bind(":st", cast(ubyte) toStatus); 1694 stmt.get.execute; 1695 } 1696 1697 /// Copy the timeout mutants to the worklist of mutants to test. 1698 void copyMutantTimeoutWorklist(const long prio = 100) @trusted { 1699 immutable sql = format!"INSERT OR IGNORE INTO %1$s (id,prio) 1700 SELECT id,%3$s FROM %2$s"(mutantWorklistTable, 1701 mutantTimeoutWorklistTable, prio); 1702 auto stmt = db.prepare(sql); 1703 stmt.get.execute; 1704 } 1705 1706 /** Update the content of metadata tables with what has been added to the 1707 * raw table data. 1708 */ 1709 void updateMetadata() @trusted { 1710 db.run(format!"DELETE FROM %s"(srcMetadataTable)); 1711 db.run(format!"DELETE FROM %s"(nomutTable)); 1712 db.run(format!"DELETE FROM %s"(nomutDataTable)); 1713 1714 static immutable nomut_tbl = "INSERT INTO %s 1715 SELECT 1716 t0.id mp_id, 1717 t1.line line, 1718 count(*) status 1719 FROM %s t0, %s t1 1720 WHERE 1721 t0.file_id = t1.file_id AND 1722 (t1.line BETWEEN t0.line AND t0.line_end) 1723 GROUP BY 1724 t0.id"; 1725 db.run(format!nomut_tbl(nomutTable, mutationPointTable, rawSrcMetadataTable)); 1726 1727 static immutable src_metadata_sql = "INSERT INTO %s 1728 SELECT DISTINCT 1729 t0.id AS mut_id, 1730 t1.id AS st_id, 1731 t2.id AS mp_id, 1732 t3.id AS file_id, 1733 (SELECT count(*) FROM %s WHERE nomut.mp_id = t2.id) as nomut 1734 FROM %s t0, %s t1, %s t2, %s t3 1735 WHERE 1736 t0.mp_id = t2.id AND 1737 t0.st_id = t1.id AND 1738 t2.file_id = t3.id"; 1739 db.run(format!src_metadata_sql(srcMetadataTable, nomutTable, 1740 mutationTable, mutationStatusTable, mutationPointTable, filesTable)); 1741 1742 static immutable nomut_data_tbl = "INSERT INTO %s 1743 SELECT 1744 t0.id as mut_id, 1745 t0.mp_id as mp_id, 1746 t1.line as line, 1747 t1.tag as tag, 1748 t1.comment as comment 1749 FROM %s t0, %s t1, %s t2 1750 WHERE 1751 t0.mp_id = t2.mp_id AND 1752 t1.line = t2.line"; 1753 db.run(format!nomut_data_tbl(nomutDataTable, mutationTable, 1754 rawSrcMetadataTable, nomutTable)); 1755 } 1756 1757 /// Returns: all schematas excluding those that are known to not be 1758 /// possible to compile. 1759 SchemataId[] getSchematas() @trusted { 1760 static immutable sql = format!"SELECT t0.id 1761 FROM %1$s t0 1762 WHERE 1763 t0.id NOT IN (SELECT id FROM %2$s)"( 1764 schemataTable, schemataUsedTable); 1765 auto stmt = db.prepare(sql); 1766 auto app = appender!(SchemataId[])(); 1767 foreach (a; stmt.get.execute) { 1768 app.put(SchemataId(a.peek!long(0))); 1769 } 1770 return app.data; 1771 } 1772 1773 Nullable!Schemata getSchemata(SchemataId id) @trusted { 1774 import std.zlib : uncompress; 1775 1776 static immutable sql = format!"SELECT 1777 t1.path, t0.text, t0.offset_begin, t0.offset_end 1778 FROM %1$s t0, %2$s t1 1779 WHERE 1780 t0.schem_id = :id AND 1781 t0.file_id = t1.id 1782 ORDER BY t0.order_ ASC 1783 "(schemataFragmentTable, filesTable); 1784 1785 typeof(return) rval; 1786 auto stmt = db.prepare(sql); 1787 stmt.get.bind(":id", cast(long) id); 1788 1789 auto app = appender!(SchemataFragment[])(); 1790 foreach (a; stmt.get.execute) { 1791 auto raw = a.peek!(ubyte[])(1); 1792 auto offset = Offset(a.peek!uint(2), a.peek!uint(3)); 1793 app.put(SchemataFragment(a.peek!string(0).Path, offset, 1794 cast(const(ubyte)[]) uncompress(raw, offset.end - offset.begin))); 1795 } 1796 1797 if (!app.data.empty) { 1798 rval = Schemata(SchemataId(id), app.data); 1799 } 1800 1801 return rval; 1802 } 1803 1804 /// Returns: number of mutants in a schemata that are marked for testing. 1805 long schemataMutantsCount(const SchemataId id, const Mutation.Kind[] kinds) @trusted { 1806 const sql = format!"SELECT count(*) 1807 FROM %s t1, %s t2, %s t3, %s t4 1808 WHERE 1809 t1.schem_id = :id AND 1810 t1.st_id = t2.id AND 1811 t3.st_id = t1.st_id AND 1812 t2.id = t4.id AND 1813 t3.kind IN (%(%s,%)) 1814 "(schemataMutantTable, mutationStatusTable, 1815 mutationTable, mutantWorklistTable, kinds.map!(a => cast(int) a)); 1816 1817 auto stmt = db.prepare(sql); 1818 stmt.get.bind(":id", id.get); 1819 return stmt.get.execute.oneValue!long; 1820 } 1821 1822 MutationStatusId[] getSchemataMutants(const SchemataId id, const Mutation.Kind[] kinds) @trusted { 1823 // TODO: DISTINCT should not be needed. Instead use e.g. a constraint on the table or something 1824 immutable sql = format!"SELECT DISTINCT t1.st_id 1825 FROM %s t1, %s t2, %s t3, %s t4 1826 WHERE 1827 t1.schem_id = :id AND 1828 t1.st_id = t2.id AND 1829 t3.st_id = t1.st_id AND 1830 t2.id = t4.id AND 1831 t3.kind IN (%(%s,%)) 1832 "(schemataMutantTable, mutationStatusTable, 1833 mutationTable, mutantWorklistTable, kinds.map!(a => cast(int) a)); 1834 auto stmt = db.prepare(sql); 1835 stmt.get.bind(":id", id.get); 1836 1837 auto app = appender!(MutationStatusId[])(); 1838 foreach (a; stmt.get.execute) { 1839 app.put(a.peek!long(0).MutationStatusId); 1840 } 1841 1842 return app.data; 1843 } 1844 1845 /// Returns: the kind of mutants a schemata contains. 1846 Mutation.Kind[] getSchemataKinds(const SchemataId id) @trusted { 1847 static immutable sql = format!"SELECT DISTINCT t1.kind 1848 FROM %1$s t0, %2$s t1 1849 WHERE 1850 t0.schem_id = :id AND 1851 t0.st_id = t1.st_id 1852 "(schemataMutantTable, mutationTable); 1853 auto stmt = db.prepare(sql); 1854 stmt.get.bind(":id", cast(long) id); 1855 1856 auto app = appender!(Mutation.Kind[])(); 1857 foreach (a; stmt.get.execute) { 1858 app.put(a.peek!long(0).to!(Mutation.Kind)); 1859 } 1860 1861 return app.data; 1862 } 1863 1864 /// Mark a schemata as used. 1865 void markUsed(const SchemataId id) @trusted { 1866 static immutable sql = format!"INSERT OR IGNORE INTO %1$s VALUES(:id)"(schemataUsedTable); 1867 auto stmt = db.prepare(sql); 1868 stmt.get.bind(":id", cast(long) id); 1869 stmt.get.execute; 1870 } 1871 1872 /// Create a schemata from a bundle of fragments. 1873 Nullable!SchemataId putSchemata(SchemataChecksum cs, 1874 const SchemataFragment[] fragments, MutationStatusId[] mutants) @trusted { 1875 import std.range : enumerate; 1876 import std.zlib : compress; 1877 1878 const schemId = cast(long) cs.value.c0; 1879 1880 const exists = () { 1881 static immutable sql = format!"SELECT count(*) FROM %1$s WHERE id=:id"(schemataTable); 1882 auto stmt = db.prepare(sql); 1883 stmt.get.bind(":id", schemId); 1884 return stmt.get.execute.oneValue!long != 0; 1885 1886 }(); 1887 1888 if (exists) 1889 return typeof(return)(); 1890 1891 { 1892 static immutable sql = format!"INSERT INTO %1$s VALUES(:id, :nr)"(schemataTable); 1893 auto stmt = db.prepare(sql); 1894 stmt.get.bind(":id", cast(long) cs.value.c0); 1895 stmt.get.bind(":nr", cast(long) fragments.length); 1896 stmt.get.execute; 1897 } 1898 1899 foreach (f; fragments.enumerate) { 1900 const fileId = getFileId(f.value.file); 1901 if (fileId.isNull) { 1902 logger.warningf("Unable to add schemata fragment for file %s because it doesn't exist", 1903 f.value.file); 1904 continue; 1905 } 1906 1907 db.run(insert!SchemataFragmentTable, SchemataFragmentTable(0, 1908 schemId, cast(long) fileId.get, f.index, 1909 compress(f.value.text), f.value.offset.begin, f.value.offset.end)); 1910 } 1911 1912 // relate mutants to this schemata. 1913 db.run(insertOrIgnore!SchemataMutantTable, 1914 mutants.map!(a => SchemataMutantTable(cast(long) a, schemId))); 1915 1916 return typeof(return)(schemId.SchemataId); 1917 } 1918 1919 /// Prunes the database of schemas that where created by an older version. 1920 void deleteAllSchemas() @trusted { 1921 db.run(delete_!SchemataTable); 1922 } 1923 1924 /// Prunes the database of schemas that are unusable. 1925 void pruneSchemas() @trusted { 1926 auto remove = () { 1927 auto remove = appender!(long[])(); 1928 1929 // remove those that have lost some fragments 1930 static immutable sqlFragment = format!"SELECT t0.id 1931 FROM 1932 %1$s t0, 1933 (SELECT schem_id id,count(*) fragments FROM %2$s GROUP BY schem_id) t1 1934 WHERE 1935 t0.id = t1.id AND 1936 t0.fragments != t1.fragments 1937 "(schemataTable, 1938 schemataFragmentTable); 1939 auto stmt = db.prepare(sqlFragment); 1940 foreach (a; stmt.get.execute) { 1941 remove.put(a.peek!long(0)); 1942 } 1943 1944 // remove those that have lost all fragments 1945 static immutable sqlNoFragment = format!"SELECT t0.id FROM %1$s t0 WHERE t0.id NOT IN (SELECT schem_id FROM %2$s)"( 1946 schemataTable, schemataFragmentTable); 1947 stmt = db.prepare(sqlNoFragment); 1948 foreach (a; stmt.get.execute) { 1949 remove.put(a.peek!long(0)); 1950 } 1951 1952 return remove.data; 1953 }(); 1954 1955 static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable); 1956 auto stmt = db.prepare(sql); 1957 foreach (a; remove) { 1958 stmt.get.bind(":id", a); 1959 stmt.get.execute; 1960 stmt.get.reset; 1961 } 1962 } 1963 1964 /** Removes all schemas that either do not compile or have zero mutants. 1965 * 1966 * Returns: number of schemas removed. 1967 */ 1968 long pruneUsedSchemas() @trusted { 1969 auto remove = () { 1970 auto remove = appender!(long[])(); 1971 1972 static immutable sqlUsed = format!"SELECT id FROM %1$s"(schemataUsedTable); 1973 auto stmt = db.prepare(sqlUsed); 1974 foreach (a; stmt.get.execute) { 1975 remove.put(a.peek!long(0)); 1976 } 1977 return remove.data; 1978 }(); 1979 1980 static immutable sql = format!"DELETE FROM %1$s WHERE id=:id"(schemataTable); 1981 auto stmt = db.prepare(sql); 1982 foreach (a; remove) { 1983 stmt.get.bind(":id", a); 1984 stmt.get.execute; 1985 stmt.get.reset; 1986 } 1987 1988 return remove.length; 1989 } 1990 1991 /// Compact the database by running a VACUUM operation 1992 void vacuum() @trusted { 1993 db.run("VACUUM"); 1994 } 1995 1996 /// Returns: the stored runtimes in ascending order by their `timeStamp`. 1997 TestCmdRuntime[] getTestCmdRuntimes() @trusted { 1998 import std.algorithm : sort; 1999 2000 auto app = appender!(TestCmdRuntime[])(); 2001 foreach (r; db.run(select!RuntimeHistoryTable)) { 2002 app.put(TestCmdRuntime(r.timeStamp, r.timeMs.dur!"msecs")); 2003 } 2004 2005 return app.data.sort!((a, b) => a.timeStamp < b.timeStamp).array; 2006 } 2007 2008 /// Drop all currently stored runtimes and replaces with `runtime`. 2009 void setTestCmdRuntimes(const TestCmdRuntime[] runtimes) @trusted { 2010 import std.range : enumerate; 2011 2012 db.run(format!"DELETE FROM %s"(runtimeHistoryTable)); 2013 db.run(insertOrReplace!RuntimeHistoryTable, 2014 runtimes.enumerate.map!(a => RuntimeHistoryTable(a.index, 2015 a.value.timeStamp, a.value.runtime.total!"msecs"))); 2016 } 2017 2018 /// Returns: the stored scores in ascending order by their `time`. 2019 MutationScore[] getMutationScoreHistory() @trusted { 2020 import std.algorithm : sort; 2021 2022 auto app = appender!(MutationScore[])(); 2023 foreach (r; db.run(select!MutationScoreHistoryTable)) { 2024 app.put(MutationScore(r.timeStamp, typeof(MutationScore.score)(r.score))); 2025 } 2026 2027 return app.data.sort!((a, b) => a.timeStamp < b.timeStamp).array; 2028 } 2029 2030 /// Add a mutation score to the history table. 2031 void putMutationScore(const MutationScore score) @trusted { 2032 db.run(insert!MutationScoreHistoryTable, MutationScoreHistoryTable(0, 2033 score.timeStamp, score.score.get)); 2034 } 2035 2036 /// Trim the mutation score history table to only contain the last `keep` scores. 2037 void trimMutationScore(const long keep) @trusted { 2038 auto stmt = db.prepare(format!"SELECT count(*) FROM %s"(mutationScoreHistoryTable)); 2039 const sz = stmt.get.execute.oneValue!long; 2040 2041 if (sz < keep) 2042 return; 2043 2044 auto ids = appender!(long[])(); 2045 stmt = db.prepare(format!"SELECT t0.id FROM t0 %s ORDER BY t0.time ASC LIMIT :limit"( 2046 mutationScoreHistoryTable)); 2047 stmt.get.bind(":limit", sz - keep); 2048 foreach (a; stmt.get.execute) 2049 ids.put(a.peek!long(0)); 2050 2051 stmt = db.prepare(format!"DELETE FROM %s WHERE id = :id"(mutationScoreHistoryTable)); 2052 foreach (a; ids.data) { 2053 stmt.get.bind(":id", a); 2054 stmt.get.execute; 2055 stmt.get.reset; 2056 } 2057 } 2058 2059 /// Add coverage regions. 2060 void putCoverageMap(const FileId id, const Offset[] region) @trusted { 2061 static immutable sql = format!"INSERT OR IGNORE INTO %1$s (file_id, begin, end) 2062 VALUES(:fid, :begin, :end)"(srcCovTable); 2063 auto stmt = db.prepare(sql); 2064 2065 foreach (a; region) { 2066 stmt.get.bind(":fid", id.get); 2067 stmt.get.bind(":begin", a.begin); 2068 stmt.get.bind(":end", a.end); 2069 stmt.get.execute; 2070 stmt.get.reset; 2071 } 2072 } 2073 2074 CovRegion[][FileId] getCoverageMap() @trusted { 2075 static immutable sql = format!"SELECT file_id,begin,end,id FROM %s"(srcCovTable); 2076 auto stmt = db.prepare(sql); 2077 2078 typeof(return) rval; 2079 foreach (ref r; stmt.get.execute) { 2080 auto region = CovRegion(r.peek!long(3).CoverageRegionId, 2081 Offset(r.peek!uint(1), r.peek!uint(2))); 2082 if (auto v = FileId(r.peek!long(0)) in rval) { 2083 *v ~= region; 2084 } else { 2085 rval[FileId(r.peek!long(0))] = [region]; 2086 } 2087 } 2088 2089 return rval; 2090 } 2091 2092 long getCoverageMapCount() @trusted { 2093 static immutable sql = format!"SELECT count(*) FROM %s"(srcCovTable); 2094 auto stmt = db.prepare(sql); 2095 foreach (ref r; stmt.get.execute) 2096 return r.peek!long(0); 2097 return 0; 2098 } 2099 2100 void clearCoverageMap(const FileId id) @trusted { 2101 static immutable sql = format!"DELETE FROM %1$s WHERE file_id = :id"(srcCovTable); 2102 auto stmt = db.prepare(sql); 2103 stmt.get.bind(":id", id.get); 2104 stmt.get.execute; 2105 } 2106 2107 void putCoverageInfo(const CoverageRegionId regionId, bool status) { 2108 static immutable sql = format!"INSERT OR REPLACE INTO %1$s (id, status) VALUES(:id, :status)"( 2109 srcCovInfoTable); 2110 auto stmt = db.prepare(sql); 2111 stmt.get.bind(":id", regionId.get); 2112 stmt.get.bind(":status", status); 2113 stmt.get.execute; 2114 } 2115 2116 Optional!SysTime getCoverageTimeStamp() @trusted { 2117 static immutable sql = format!"SELECT timeStamp FROM %s"(srcCovTimeStampTable); 2118 auto stmt = db.prepare(sql); 2119 2120 foreach (ref r; stmt.get.execute) { 2121 return some(r.peek!string(0).fromSqLiteDateTime); 2122 } 2123 return none!SysTime; 2124 } 2125 2126 /// Set the timestamp to the current UTC time. 2127 void updateCoverageTimeStamp() @trusted { 2128 static immutable sql = format!"INSERT OR REPLACE INTO %s (id, timestamp) VALUES(0, :time)"( 2129 srcCovTimeStampTable); 2130 auto stmt = db.prepare(sql); 2131 stmt.get.bind(":time", Clock.currTime.toSqliteDateTime); 2132 stmt.get.execute; 2133 } 2134 2135 /// Returns: the latest/newest timestamp of the tracked SUT or test files. 2136 Optional!SysTime getLatestTimeStampOfTestOrSut() @trusted { 2137 import std.algorithm : max; 2138 2139 auto test = getNewestTestFile; 2140 auto sut = getNewestFile; 2141 2142 if (!(test.hasValue || sut.hasValue)) 2143 return none!SysTime; 2144 2145 return some(max(test.orElse(TestFile.init).timeStamp, sut.orElse(SysTime.init))); 2146 } 2147 2148 MutationStatusId[] getNotCoveredMutants() @trusted { 2149 static immutable sql = format!"SELECT DISTINCT t3.st_id FROM %1$s t0, %2$s t1, %3$s t2, %4$s t3 2150 WHERE t0.status = 0 AND 2151 t0.id = t1.id AND 2152 t1.file_id = t2.file_id AND 2153 (t2.offset_begin BETWEEN t1.begin AND t1.end) AND 2154 (t2.offset_end BETWEEN t1.begin AND t1.end) AND 2155 t2.id = t3.mp_id"(srcCovInfoTable, 2156 srcCovTable, mutationPointTable, mutationTable); 2157 2158 auto app = appender!(MutationStatusId[])(); 2159 auto stmt = db.prepare(sql); 2160 foreach (ref r; stmt.get.execute) { 2161 app.put(MutationStatusId(r.peek!long(0))); 2162 } 2163 2164 return app.data; 2165 } 2166 2167 DbDependency dependencyApi() return { 2168 return DbDependency(&this); 2169 } 2170 2171 DbTestCmd testCmdApi() return { 2172 return DbTestCmd(&this); 2173 } 2174 } 2175 2176 /** Dependencies between root and those files that should trigger a re-analyze 2177 * of the root if they are changed. 2178 */ 2179 struct DbDependency { 2180 private Database* db; 2181 2182 /// The root must already exist or the whole operation will fail with an sql error. 2183 void set(const Path path, const DepFile[] deps) @trusted { 2184 static immutable insertDepSql = format!"INSERT OR IGNORE INTO %1$s (file,checksum0,checksum1) 2185 VALUES(:file,:cs0,:cs1) 2186 ON CONFLICT (file) DO UPDATE SET checksum0=:cs0,checksum1=:cs1 WHERE file=:file"( 2187 depFileTable); 2188 2189 auto stmt = db.prepare(insertDepSql); 2190 auto ids = appender!(long[])(); 2191 foreach (a; deps) { 2192 stmt.get.bind(":file", a.file.toString); 2193 stmt.get.bind(":cs0", cast(long) a.checksum.c0); 2194 stmt.get.bind(":cs1", cast(long) a.checksum.c1); 2195 stmt.get.execute; 2196 stmt.get.reset; 2197 2198 // can't use lastInsertRowid because a conflict would not update 2199 // the ID. 2200 auto id = getId(a.file); 2201 if (id.hasValue) 2202 ids.put(id.orElse(0L)); 2203 } 2204 2205 static immutable addRelSql = format!"INSERT OR IGNORE INTO %1$s (dep_id,file_id) VALUES(:did, :fid)"( 2206 depRootTable); 2207 stmt = db.prepare(addRelSql); 2208 const fid = () { 2209 auto a = db.getFileId(path); 2210 if (a.isNull) { 2211 throw new Exception( 2212 "File is not tracked (is missing from the files table in the database) " 2213 ~ path); 2214 } 2215 return a.get; 2216 }(); 2217 2218 foreach (id; ids.data) { 2219 stmt.get.bind(":did", id); 2220 stmt.get.bind(":fid", fid.get); 2221 stmt.get.execute; 2222 stmt.get.reset; 2223 } 2224 } 2225 2226 private Optional!long getId(const Path file) { 2227 foreach (a; db.run(select!DependencyFileTable.where("file = :file", 2228 Bind("file")), file.toString)) { 2229 return some(a.id); 2230 } 2231 return none!long; 2232 } 2233 2234 /// Returns: all dependencies. 2235 DepFile[] getAll() @trusted { 2236 return db.run(select!DependencyFileTable) 2237 .map!(a => DepFile(Path(a.file), Checksum(a.checksum0, a.checksum1))).array; 2238 } 2239 2240 /// Returns: all files that a root is dependent on. 2241 Path[] get(const Path root) @trusted { 2242 static immutable sql = format!"SELECT t0.file 2243 FROM %1$s t0, %2$s t1, %3$s t2 2244 WHERE 2245 t0.id = t1.dep_id AND 2246 t1.file_id = t2.id AND 2247 t2.path = :file"(depFileTable, 2248 depRootTable, filesTable); 2249 2250 auto stmt = db.prepare(sql); 2251 stmt.get.bind(":file", root.toString); 2252 auto app = appender!(Path[])(); 2253 foreach (ref a; stmt.get.execute) { 2254 app.put(Path(a.peek!string(0))); 2255 } 2256 2257 return app.data; 2258 } 2259 2260 /// Remove all dependencies that have no relation to a root. 2261 void cleanup() @trusted { 2262 db.run(format!"DELETE FROM %1$s 2263 WHERE id NOT IN (SELECT dep_id FROM %2$s)"(depFileTable, 2264 depRootTable)); 2265 } 2266 2267 } 2268 2269 struct DbTestCmd { 2270 import my.hash : Checksum64; 2271 2272 private Database* db; 2273 2274 void set(string testCmd, ChecksumTestCmdOriginal cs) @trusted { 2275 static immutable sql = format!"INSERT OR IGNORE INTO %1$s (checksum, cmd) VALUES(:cs, :cmd)"( 2276 testCmdOriginalTable); 2277 2278 auto stmt = db.prepare(sql); 2279 stmt.get.bind(":cs", cast(long) cs.get.c0); 2280 stmt.get.bind(":cmd", testCmd); 2281 stmt.get.execute; 2282 } 2283 2284 void removeOriginal(string testCmd) @trusted { 2285 static immutable sql = "DELETE FROM " ~ testCmdOriginalTable ~ " WHERE cmd = :cmd"; 2286 auto stmt = db.prepare(sql); 2287 stmt.get.bind(":cmd", testCmd); 2288 stmt.get.execute; 2289 } 2290 2291 void remove(ChecksumTestCmdOriginal cs) @trusted { 2292 static immutable sql = "DELETE FROM " ~ testCmdOriginalTable ~ " WHERE checksum = :cs"; 2293 auto stmt = db.prepare(sql); 2294 stmt.get.bind(":cs", cast(long) cs.get.c0); 2295 stmt.get.execute; 2296 } 2297 2298 Set!Checksum64 original() @trusted { 2299 static immutable sql = "SELECT checksum FROM " ~ testCmdOriginalTable; 2300 2301 auto stmt = db.prepare(sql); 2302 typeof(return) rval; 2303 foreach (ref r; stmt.get.execute) 2304 rval.add(Checksum64(cast(ulong) r.peek!long(0))); 2305 return rval; 2306 } 2307 2308 void add(ChecksumTestCmdMutated cs, Mutation.Status status) @trusted { 2309 static immutable sql = format!"INSERT OR REPLACE INTO %1$s (checksum,status,timestamp) VALUES(:cs,:status,:ts)"( 2310 testCmdMutatedTable); 2311 2312 auto stmt = db.prepare(sql); 2313 stmt.get.bind(":cs", cast(long) cs.get.c0); 2314 stmt.get.bind(":status", cast(long) status); 2315 stmt.get.bind(":ts", Clock.currTime.toSqliteDateTime); 2316 stmt.get.execute; 2317 } 2318 2319 /// Trim the saved checksums to only the latest `keep`. 2320 void trimMutated(const long keep) @trusted { 2321 auto stmt = db.prepare(format!"SELECT count(*) FROM %s"(testCmdMutatedTable)); 2322 const sz = stmt.get.execute.oneValue!long; 2323 if (sz < keep) 2324 return; 2325 2326 auto ids = appender!(long[])(); 2327 stmt = db.prepare(format!"SELECT checksum FROM %s ORDER BY timestamp ASC LIMIT :limit"( 2328 testCmdMutatedTable)); 2329 stmt.get.bind(":limit", sz - keep); 2330 foreach (a; stmt.get.execute) 2331 ids.put(a.peek!long(0)); 2332 2333 stmt = db.prepare(format!"DELETE FROM %s WHERE checksum = :cs"(testCmdMutatedTable)); 2334 foreach (a; ids.data) { 2335 stmt.get.bind(":cs", a); 2336 stmt.get.execute; 2337 stmt.get.reset; 2338 } 2339 } 2340 2341 Mutation.Status[Checksum64] mutated() @trusted { 2342 static immutable sql = "SELECT checksum,status FROM " ~ testCmdMutatedTable; 2343 2344 auto stmt = db.prepare(sql); 2345 typeof(return) rval; 2346 foreach (ref r; stmt.get.execute) 2347 rval[Checksum64(cast(ulong) r.peek!long(0))] = r.peek!long(1).to!(Mutation.Status); 2348 return rval; 2349 } 2350 } 2351 2352 private: 2353 2354 MarkedMutant make(MarkedMutantTbl m) { 2355 import dextool.plugin.mutate.backend.type; 2356 2357 return MarkedMutant(m.mutationStatusId.MutationStatusId, Checksum(m.checksum0, 2358 m.checksum1), m.mutationId.MutationId, SourceLoc(m.line, m.column), 2359 m.path.Path, m.toStatus.to!(Mutation.Status), m.time, 2360 m.rationale.Rationale, m.mutText); 2361 } 2362 2363 string fromOrder(const MutationOrder userOrder) { 2364 final switch (userOrder) { 2365 case MutationOrder.random: 2366 return ":base_prio + t1.prio + abs(random() % 100)"; 2367 case MutationOrder.consecutive: 2368 return ":base_prio"; 2369 case MutationOrder.bySize: 2370 return ":base_prio + t1.prio"; 2371 } 2372 }