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