1 /** 2 Copyright: Copyright (c) 2018, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 10 This module contains 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; 25 import std.array : Appender, appender, array; 26 import std.conv : to; 27 import std.datetime : SysTime, Clock; 28 import std.exception : collectException; 29 import std.format : format; 30 import std.path : relativePath; 31 import std.regex : Regex, matchFirst; 32 import std.typecons : Nullable, Flag, No; 33 34 import miniorm : toSqliteDateTime, fromSqLiteDateTime; 35 36 import dextool.type : AbsolutePath, Path, ExitStatusType; 37 38 import dextool.plugin.mutate.backend.database.schema; 39 import dextool.plugin.mutate.backend.database.type; 40 import dextool.plugin.mutate.backend.type : Language; 41 42 /** Database wrapper with minimal dependencies. 43 */ 44 struct Database { 45 import miniorm : Miniorm, select, insert, insertOrReplace, delete_; 46 import d2sqlite3 : SqlDatabase = Database; 47 import dextool.plugin.mutate.backend.type : MutationPoint, Mutation, Checksum; 48 49 Miniorm db; 50 alias db this; 51 52 /** Create a database by either opening an existing or initializing a new. 53 * 54 * Params: 55 * db = path to the database 56 */ 57 static auto make(string db) @safe { 58 return Database(initializeDB(db)); 59 } 60 61 /// If the file has already been analyzed. 62 bool isAnalyzed(const Path p) @trusted { 63 auto stmt = db.prepare("SELECT count(*) FROM files WHERE path=:path LIMIT 1"); 64 stmt.bind(":path", cast(string) p); 65 auto res = stmt.execute; 66 return res.oneValue!long != 0; 67 } 68 69 /// If the file has already been analyzed. 70 bool isAnalyzed(const Path p, const Checksum cs) @trusted { 71 auto stmt = db.prepare( 72 "SELECT count(*) FROM files WHERE path=:path AND checksum0=:cs0 AND checksum1=:cs1 LIMIT 1"); 73 stmt.bind(":path", cast(string) p); 74 stmt.bind(":cs0", cs.c0); 75 stmt.bind(":cs1", cs.c1); 76 auto res = stmt.execute; 77 return res.oneValue!long != 0; 78 } 79 80 bool exists(MutationStatusId status_id) { 81 immutable s = format!"SELECT COUNT(*) FROM %s WHERE id=:id LIMIT 1"(mutationStatusTable); 82 auto stmt = db.prepare(s); 83 stmt.bind(":id", cast(long) status_id); 84 auto res = stmt.execute; 85 return res.oneValue!long == 0; 86 } 87 88 bool isMarked(MutationId id) @trusted { 89 immutable s = format!"SELECT COUNT(*) FROM %s WHERE st_id IN 90 (SELECT st_id FROM %s WHERE id=:id)"( 91 markedMutantTable, mutationTable); 92 auto stmt = db.prepare(s); 93 stmt.bind(":id", cast(long) id); 94 auto res = stmt.execute; 95 return res.oneValue!long != 0; 96 } 97 98 MarkedMutant[] getLostMarkings() @trusted { 99 import std.algorithm : filter; 100 return getMarkedMutants.filter!(a => exists(MutationStatusId(a.mutationStatusId))).array; 101 } 102 103 Nullable!FileId getFileId(const Path p) @trusted { 104 enum sql = format("SELECT id FROM %s WHERE path=:path", filesTable); 105 auto stmt = db.prepare(sql); 106 stmt.bind(":path", cast(string) p); 107 auto res = stmt.execute; 108 109 typeof(return) rval; 110 if (!res.empty) 111 rval = FileId(res.oneValue!long); 112 return rval; 113 } 114 115 /// Returns: the path ID for the mutant. 116 Nullable!FileId getFileId(const MutationId id) @trusted { 117 enum sql = format("SELECT t1.file_id 118 FROM %s t0, %s t1 119 WHERE t0.id = :id AND t0.mp_id = t1.id", 120 mutationTable, mutationPointTable); 121 auto stmt = db.prepare(sql); 122 stmt.bind(":id", cast(long) id); 123 124 typeof(return) rval; 125 foreach (r; stmt.execute) 126 rval = FileId(r.peek!long(0)); 127 return rval; 128 } 129 130 /// Returns: the file path that the id correspond to. 131 Nullable!Path getFile(const FileId id) @trusted { 132 enum sql = format("SELECT path FROM %s WHERE id = :id", filesTable); 133 auto stmt = db.prepare(sql); 134 stmt.bind(":id", cast(long) id); 135 136 typeof(return) rval; 137 foreach (r; stmt.execute) 138 rval = Path(r.peek!string(0)); 139 return rval; 140 } 141 142 /// Remove the file with all mutations that are coupled to it. 143 void removeFile(const Path p) @trusted { 144 auto stmt = db.prepare(format!"DELETE FROM %s WHERE path=:path"(filesTable)); 145 stmt.bind(":path", cast(string) p); 146 stmt.execute; 147 } 148 149 /// Returns: All files in the database as relative paths. 150 Path[] getFiles() @trusted { 151 auto stmt = db.prepare(format!"SELECT path from %s"(filesTable)); 152 auto res = stmt.execute; 153 154 auto app = appender!(Path[]); 155 foreach (ref r; res) { 156 app.put(Path(r.peek!string(0))); 157 } 158 159 return app.data; 160 } 161 162 enum CntAction { 163 /// Increment the counter 164 incr, 165 /// Reset the counter to zero 166 reset, 167 } 168 169 /** Update the status of a mutant. 170 * 171 * Params: 172 * id = ID of the mutant 173 * st = status to broadcast 174 * d = time spent on veryfing the mutant 175 * tcs = test cases that killed the mutant 176 * counter = how to act with the counter 177 */ 178 void updateMutation(const MutationId id, const Mutation.Status st, 179 const Duration d, const(TestCase)[] tcs, CntAction counter = CntAction.incr) @trusted { 180 enum sql = "UPDATE %s SET 181 status=:st,time=:time,update_ts=:update_ts,%s 182 WHERE 183 id IN (SELECT st_id FROM %s WHERE id = :id)"; 184 185 auto stmt = () { 186 final switch (counter) { 187 case CntAction.incr: 188 return db.prepare(format!sql(mutationStatusTable, 189 "test_cnt=test_cnt+1", mutationTable)); 190 case CntAction.reset: 191 return db.prepare(format!sql(mutationStatusTable, 192 "test_cnt=0", mutationTable)); 193 } 194 }(); 195 196 stmt.bind(":st", st.to!long); 197 stmt.bind(":id", id.to!long); 198 stmt.bind(":time", d.total!"msecs"); 199 stmt.bind(":update_ts", Clock.currTime.toUTC.toSqliteDateTime); 200 stmt.execute; 201 202 updateMutationTestCases(id, tcs); 203 } 204 205 /** Update the counter of how many times the mutants has been alive. 206 * 207 * Params: 208 * id = ID of the mutant 209 * counter = how to act with the counter 210 */ 211 void updateMutation(const MutationStatusId id, const CntAction counter) @trusted { 212 enum sql = "UPDATE %s SET %s WHERE id = :id"; 213 214 auto stmt = () { 215 final switch (counter) { 216 case CntAction.incr: 217 return db.prepare(format!sql(mutationStatusTable, 218 "test_cnt=test_cnt+1")); 219 case CntAction.reset: 220 return db.prepare(format!sql(mutationStatusTable, "test_cnt=0")); 221 } 222 }(); 223 224 stmt.bind(":id", id.to!long); 225 stmt.execute; 226 } 227 228 /// Update the time used to test the mutant. 229 void updateMutation(const MutationStatusId id, const Duration testTime) @trusted { 230 enum sql = format!"UPDATE %s SET time=:time WHERE id = :id"(mutationStatusTable); 231 auto stmt = db.prepare(sql); 232 stmt.bind(":id", id.to!long); 233 stmt.bind(":time", testTime.total!"msecs"); 234 stmt.execute; 235 } 236 237 /** Update the status of a mutant. 238 * 239 * Params: 240 * id = mutation status ID 241 * st = new status 242 * update_ts = if the update timestamp should be updated. 243 */ 244 void updateMutationStatus(const MutationStatusId id, const Mutation.Status st, 245 Flag!"updateTs" update_ts = No.updateTs) @trusted { 246 247 auto stmt = () { 248 if (update_ts) { 249 const ts = Clock.currTime.toUTC.toSqliteDateTime; 250 auto s = db.prepare(format!"UPDATE %s SET status=:st,update_ts=:update_ts WHERE id=:id"( 251 mutationStatusTable)); 252 s.bind(":update_ts", ts); 253 return s; 254 } else 255 return db.prepare(format!"UPDATE %s SET status=:st WHERE id=:id"( 256 mutationStatusTable)); 257 }(); 258 stmt.bind(":st", st.to!long); 259 stmt.bind(":id", id.to!long); 260 stmt.execute; 261 } 262 263 /// Returns: all mutation status IDs. 264 MutationStatusId[] getAllMutationStatus() @trusted { 265 enum sql = format("SELECT id FROM %s", mutationStatusTable); 266 267 auto app = appender!(MutationStatusId[])(); 268 foreach (r; db.prepare(sql).execute) 269 app.put(MutationStatusId(r.peek!long(0))); 270 return app.data; 271 } 272 273 Nullable!(Mutation.Status) getMutationStatus(const MutationStatusId id) @trusted { 274 enum sql = format!"SELECT status FROM %s WHERE id=:id"(mutationStatusTable); 275 auto stmt = db.prepare(sql); 276 stmt.bind(":id", cast(long) id); 277 278 typeof(return) rval; 279 foreach (a; stmt.execute) { 280 rval = cast(Mutation.Status) a.peek!long(0); 281 } 282 return rval; 283 } 284 285 Nullable!MutationEntry getMutation(const MutationId id) @trusted { 286 import dextool.plugin.mutate.backend.type; 287 import dextool.type : FileName; 288 289 typeof(return) rval; 290 291 enum get_mut_sql = format("SELECT 292 t0.id, 293 t0.kind, 294 t3.time, 295 t1.offset_begin, 296 t1.offset_end, 297 t1.line, 298 t1.column, 299 t2.path, 300 t2.lang 301 FROM %s t0,%s t1,%s t2,%s t3 302 WHERE 303 t0.id == :id AND 304 t0.mp_id == t1.id AND 305 t1.file_id == t2.id AND 306 t3.id = t0.st_id 307 ", mutationTable, mutationPointTable, 308 filesTable, mutationStatusTable); 309 310 auto stmt = db.prepare(get_mut_sql); 311 stmt.bind(":id", cast(long) id); 312 auto res = stmt.execute; 313 314 if (res.empty) 315 return rval; 316 317 auto v = res.front; 318 319 auto mp = MutationPoint(Offset(v.peek!uint(3), v.peek!uint(4))); 320 mp.mutations = [Mutation(v.peek!long(1).to!(Mutation.Kind))]; 321 auto pkey = MutationId(v.peek!long(0)); 322 auto file = Path(FileName(v.peek!string(7))); 323 auto sloc = SourceLoc(v.peek!uint(5), v.peek!uint(6)); 324 auto lang = v.peek!long(8).to!Language; 325 326 rval = MutationEntry(pkey, file, sloc, mp, v.peek!long(2).dur!"msecs", lang); 327 328 return rval; 329 } 330 331 MutantMetaData getMutantationMetaData(const MutationId id) @trusted { 332 auto rval = MutantMetaData(id); 333 foreach (res; db.run(select!NomutDataTbl.where("mut_id =", cast(long) id))) { 334 rval.set(NoMut(res.tag, res.comment)); 335 } 336 return rval; 337 } 338 339 //TODO: this is a bit inefficient. it should use a callback iterator 340 MutantMetaData[] getMutantationMetaData(const Mutation.Kind[] kinds, const Mutation 341 .Status status) @trusted { 342 const sql = format!"SELECT DISTINCT t.mut_id, t.tag, t.comment 343 FROM %s t, %s t1, %s t2 344 WHERE 345 t.mut_id = t1.id AND 346 t1.st_id = t2.id AND 347 t2.status = :status AND 348 t1.kind IN (%(%s,%)) 349 ORDER BY 350 t.mut_id"(nomutDataTable, mutationTable, 351 mutationStatusTable, kinds.map!(a => cast(long) a)); 352 auto stmt = db.prepare(sql); 353 stmt.bind(":status", cast(long) status); 354 355 auto app = appender!(MutantMetaData[])(); 356 foreach (res; stmt.execute) { 357 app.put(MutantMetaData(MutationId(res.peek!long(0)), 358 MutantAttr(NoMut(res.peek!string(1), res.peek!string(2))))); 359 } 360 return app.data; 361 } 362 363 Nullable!Path getPath(const MutationId id) @trusted { 364 enum get_path_sql = format("SELECT t2.path 365 FROM 366 %s t0, %s t1, %s t2 367 WHERE 368 t0.id = :id AND 369 t0.mp_id = t1.id AND 370 t1.file_id = t2.id 371 ", mutationTable, mutationPointTable, filesTable); 372 373 auto stmt = db.prepare(get_path_sql); 374 stmt.bind(":id", cast(long) id); 375 auto res = stmt.execute; 376 377 typeof(return) rval; 378 if (!res.empty) 379 rval = Path(res.front.peek!string(0)); 380 return rval; 381 } 382 383 /// Returns: the mutants that are connected to the mutation statuses. 384 MutantInfo[] getMutantsInfo(const Mutation.Kind[] kinds, const(MutationStatusId)[] id) @trusted { 385 const get_mutid_sql = format("SELECT t0.id,t2.status,t0.kind,t1.line,t1.column 386 FROM %s t0,%s t1, %s t2 387 WHERE 388 t0.st_id IN (%(%s,%)) AND 389 t0.st_id = t2.id AND 390 t0.kind IN (%(%s,%)) AND 391 t0.mp_id = t1.id", 392 mutationTable, mutationPointTable, 393 mutationStatusTable, id.map!(a => cast(long) a), kinds.map!(a => cast(int) a)); 394 auto stmt = db.prepare(get_mutid_sql); 395 396 auto app = appender!(MutantInfo[])(); 397 foreach (res; stmt.execute) 398 app.put(MutantInfo(MutationId(res.peek!long(0)), res.peek!long(1) 399 .to!(Mutation.Status), res.peek!long(2).to!(Mutation.Kind), 400 SourceLoc(res.peek!uint(3), res.peek!uint(4)))); 401 402 return app.data; 403 } 404 405 /// Returns: the mutants that are connected to the mutation statuses. 406 MutationId[] getMutationIds(const(Mutation.Kind)[] kinds, const(MutationStatusId)[] id) @trusted { 407 if (id.length == 0) 408 return null; 409 410 const get_mutid_sql = format("SELECT id FROM %s t0 411 WHERE 412 t0.st_id IN (%(%s,%)) AND 413 t0.kind IN (%(%s,%))", mutationTable, 414 id.map!(a => cast(long) a), kinds.map!(a => cast(int) a)); 415 auto stmt = db.prepare(get_mutid_sql); 416 417 auto app = appender!(MutationId[])(); 418 foreach (res; stmt.execute) 419 app.put(MutationId(res.peek!long(0))); 420 return app.data; 421 } 422 423 Nullable!MutationStatusId getMutationStatusId(const MutationId id) @trusted { 424 auto stmt = db.prepare(format("SELECT st_id FROM %s WHERE id=:id", mutationTable)); 425 stmt.bind(":id", cast(long) id); 426 typeof(return) rval; 427 foreach (res; stmt.execute) 428 rval = MutationStatusId(res.peek!long(0)); 429 return rval; 430 } 431 // Returns: the status of the mutant 432 Nullable!Mutation.Status getMutationStatus(const MutationId id) @trusted { 433 auto s = format!"SELECT status FROM %s WHERE id IN (SELECT st_id FROM %s WHERE id=:mut_id)"( 434 mutationStatusTable, mutationTable); 435 auto stmt = db.prepare(s); 436 stmt.bind(":mut_id", cast(long) id); 437 typeof(return) rval; 438 foreach (res; stmt.execute) 439 rval = res.peek!long(0).to!(Mutation.Status); 440 return rval; 441 } 442 443 /// Returns: the mutants in the file at the line. 444 MutationStatusId[] getMutationsOnLine(const(Mutation.Kind)[] kinds, FileId fid, SourceLoc sloc) @trusted { 445 // TODO: should it also be line_end? 446 const sql = format("SELECT DISTINCT t0.id FROM %s t0, %s t1, %s t2 447 WHERE 448 t1.st_id = t0.id AND 449 t1.kind IN (%(%s,%)) AND 450 t1.mp_id = t2.id AND 451 t2.file_id = :fid AND 452 (:line BETWEEN t2.line AND t2.line_end) 453 ", 454 mutationStatusTable, mutationTable, mutationPointTable, 455 kinds.map!(a => cast(int) a)); 456 auto stmt = db.prepare(sql); 457 stmt.bind(":fid", cast(long) fid); 458 stmt.bind(":line", sloc.line); 459 460 auto app = appender!(typeof(return))(); 461 foreach (res; stmt.execute) 462 app.put(MutationStatusId(res.peek!long(0))); 463 return app.data; 464 } 465 466 LineMetadata getLineMetadata(const FileId fid, const SourceLoc sloc) @trusted { 467 // TODO: change this select to using microrm 468 enum sql = format("SELECT nomut,tag,comment FROM %s 469 WHERE 470 file_id = :fid AND 471 line = :line", rawSrcMetadataTable); 472 auto stmt = db.prepare(sql); 473 stmt.bind(":fid", cast(long) fid); 474 stmt.bind(":line", sloc.line); 475 476 auto rval = typeof(return)(fid, sloc.line); 477 foreach (res; stmt.execute) { 478 if (res.peek!long(0) != 0) 479 rval.set(NoMut(res.peek!string(1), res.peek!string(2))); 480 } 481 482 return rval; 483 } 484 485 /// Returns: the `nr` mutants that where the longst since they where tested. 486 MutationStatusTime[] getOldestMutants(const(Mutation.Kind)[] kinds, const long nr) @trusted { 487 const sql = format("SELECT t0.id,t0.update_ts FROM %s t0, %s t1 488 WHERE 489 t0.update_ts IS NOT NULL AND 490 t1.st_id = t0.id AND 491 t1.kind IN (%(%s,%)) 492 ORDER BY t0.update_ts ASC LIMIT :limit", 493 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a)); 494 auto stmt = db.prepare(sql); 495 stmt.bind(":limit", nr); 496 497 auto app = appender!(MutationStatusTime[])(); 498 foreach (res; stmt.execute) 499 app.put(MutationStatusTime(MutationStatusId(res.peek!long(0)), 500 res.peek!string(1).fromSqLiteDateTime)); 501 return app.data; 502 } 503 504 /// Returns: the `nr` mutant with the highest count that has not been killed and existed in the system the longest. 505 MutationStatus[] getHardestToKillMutant(const(Mutation.Kind)[] kinds, 506 const Mutation.Status status, const long nr) @trusted { 507 const sql = format("SELECT t0.id,t0.status,t0.test_cnt,t0.update_ts,t0.added_ts 508 FROM %s t0, %s t1 509 WHERE 510 t0.update_ts IS NOT NULL AND 511 t0.status = :status AND 512 t1.st_id = t0.id AND 513 t1.kind IN (%(%s,%)) AND 514 t1.st_id NOT IN (SELECT st_id FROM %s WHERE nomut != 0) 515 ORDER BY 516 t0.test_cnt DESC, 517 t0.added_ts ASC, 518 t0.update_ts ASC 519 LIMIT :limit", 520 mutationStatusTable, mutationTable, kinds.map!(a => cast(int) a), srcMetadataTable); 521 auto stmt = db.prepare(sql); 522 stmt.bind(":status", cast(long) status); 523 stmt.bind(":limit", nr); 524 525 auto app = appender!(MutationStatus[])(); 526 foreach (res; stmt.execute) { 527 auto added = () { 528 auto raw = res.peek!string(4); 529 if (raw.length == 0) 530 return Nullable!SysTime(); 531 return Nullable!SysTime(raw.fromSqLiteDateTime); 532 }(); 533 534 // dfmt off 535 app.put(MutationStatus( 536 MutationStatusId(res.peek!long(0)), 537 res.peek!long(1).to!(Mutation.Status), 538 res.peek!long(2).MutantTestCount, 539 res.peek!string(3).fromSqLiteDateTime, 540 added, 541 )); 542 // dfmt on 543 } 544 545 return app.data; 546 } 547 548 /** Get SourceLoc for a specific mutation id. 549 */ 550 Nullable!SourceLoc getSourceLocation(MutationId id) @trusted { 551 auto s = format!"SELECT line, column FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE id=:mut_id)"( 552 mutationPointTable, mutationTable); 553 auto stmt = db.prepare(s); 554 stmt.bind(":mut_id", cast(long) id); 555 typeof(return) rval; 556 foreach (res; stmt.execute) 557 rval = SourceLoc(res.peek!uint(0), res.peek!uint(1)); 558 return rval; 559 } 560 561 /** Remove all mutations of kinds. 562 */ 563 void removeMutant(const Mutation.Kind[] kinds) @trusted { 564 const s = format!"DELETE FROM %s WHERE id IN (SELECT mp_id FROM %s WHERE kind IN (%(%s,%)))"( 565 mutationPointTable, mutationTable, kinds.map!(a => cast(int) a)); 566 auto stmt = db.prepare(s); 567 stmt.execute; 568 } 569 570 /** Reset all mutations of kinds with the status `st` to unknown. 571 */ 572 void resetMutant(const Mutation.Kind[] kinds, Mutation.Status st, Mutation.Status to_st) @trusted { 573 const s = format!"UPDATE %s SET status=%s WHERE status = %s AND id IN(SELECT st_id FROM %s WHERE kind IN (%(%s,%)))"( 574 mutationStatusTable, to_st.to!long, st.to!long, 575 mutationTable, kinds.map!(a => cast(int) a)); 576 auto stmt = db.prepare(s); 577 stmt.execute; 578 } 579 580 /** Mark a mutant with status and rationale (also adds metadata). 581 */ 582 void markMutant(MutationEntry m, MutationStatusId st_id, Mutation.Status s, string r, string txt) @trusted { 583 db.run(insertOrReplace!MarkedMutant, 584 MarkedMutant(st_id, m.id, m.sloc.line, m.sloc.column, m.file, s, Clock.currTime.toUTC, Rationale(r), txt)); 585 } 586 587 void removeMarkedMutant(MutationStatusId st_id) @trusted { 588 immutable condition = format!"st_id=%s"(st_id); 589 db.run(delete_!MarkedMutant.where(condition)); 590 } 591 592 MarkedMutant[] getMarkedMutants() @trusted { 593 immutable s = format!"SELECT st_id, mut_id, line, column, path, to_status, time, rationale, mut_text 594 FROM %s ORDER BY path"(markedMutantTable); 595 auto stmt = db.prepare(s); 596 597 auto app = appender!(MarkedMutant[])(); 598 foreach (res; stmt.execute) 599 app.put(MarkedMutant(res.peek!long(0), res.peek!long(1), 600 res.peek!uint(2), res.peek!uint(3), res.peek!string(4), 601 res.peek!ulong(5), res.peek!string(6).fromSqLiteDateTime, 602 Rationale(res.peek!string(7)), res.peek!string(8))); 603 return app.data; 604 } 605 606 Mutation.Kind getKind(MutationId id) @trusted { 607 immutable s = format!"SELECT kind FROM %s WHERE id=:id"(mutationTable); 608 auto stmt = db.prepare(s); 609 stmt.bind(":id", cast(long) id); 610 611 typeof(return) rval; 612 foreach (res; stmt.execute) 613 rval = res.peek!long(0).to!(Mutation.Kind); 614 return rval; 615 } 616 617 import dextool.plugin.mutate.backend.type; 618 619 alias aliveMutants = countMutants!([Mutation.Status.alive], false); 620 alias killedMutants = countMutants!([Mutation.Status.killed], false); 621 alias timeoutMutants = countMutants!([Mutation.Status.timeout], false); 622 623 /// Returns: Total that should be counted when calculating the mutation score. 624 alias totalMutants = countMutants!([ 625 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 626 ], false); 627 628 alias unknownMutants = countMutants!([Mutation.Status.unknown], false); 629 alias killedByCompilerMutants = countMutants!([ 630 Mutation.Status.killedByCompiler 631 ], false); 632 633 alias aliveSrcMutants = countMutants!([Mutation.Status.alive], true); 634 alias killedSrcMutants = countMutants!([Mutation.Status.killed], true); 635 alias timeoutSrcMutants = countMutants!([Mutation.Status.timeout], true); 636 637 /// Returns: Total that should be counted when calculating the mutation score. 638 alias totalSrcMutants = countMutants!([ 639 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 640 ], true); 641 642 alias unknownSrcMutants = countMutants!([Mutation.Status.unknown], true); 643 alias killedByCompilerSrcMutants = countMutants!([ 644 Mutation.Status.killedByCompiler 645 ], true); 646 647 /** Count the mutants with the nomut metadata. 648 * 649 * Params: 650 * status = status the mutants must be in to be counted. 651 * distinc = count based on unique source code changes. 652 * kinds = the kind of mutants to count. 653 * file = file to count mutants in. 654 */ 655 private MutationReportEntry countMutants(int[] status, bool distinct)( 656 const Mutation.Kind[] kinds, string file = null) @trusted { 657 static if (distinct) { 658 auto qq = " 659 SELECT count(*),sum(time) 660 FROM ( 661 SELECT count(*),sum(t1.time) time 662 FROM %s t0, %s t1%s 663 WHERE 664 %s 665 t0.st_id = t1.id AND 666 t1.status IN (%(%s,%)) AND 667 t0.kind IN (%(%s,%)) 668 GROUP BY t1.id)"; 669 } else { 670 auto qq = " 671 SELECT count(*),sum(t1.time) time 672 FROM %s t0, %s t1%s 673 WHERE 674 %s 675 t0.st_id = t1.id AND 676 t1.status IN (%(%s,%)) AND 677 t0.kind IN (%(%s,%))"; 678 } 679 const query = () { 680 auto fq = file.length == 0 681 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 682 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 683 mutationPointTable, filesTable); 684 return format(qq, mutationTable, mutationStatusTable, fq_from, fq, 685 status, kinds.map!(a => cast(int) a)); 686 }(); 687 688 typeof(return) rval; 689 auto stmt = db.prepare(query); 690 if (file.length != 0) 691 stmt.bind(":path", file); 692 auto res = stmt.execute; 693 if (!res.empty) 694 rval = MutationReportEntry(res.front.peek!long(0), 695 res.front.peek!long(1).dur!"msecs"); 696 return rval; 697 } 698 699 /** Count the mutants with the nomut metadata. 700 * 701 * Params: 702 * status = status the mutants must be in to be counted. 703 * distinc = count based on unique source code changes. 704 * kinds = the kind of mutants to count. 705 * file = file to count mutants in. 706 */ 707 private MetadataNoMutEntry countNoMutMutants(int[] status, bool distinct)( 708 const Mutation.Kind[] kinds, string file = null) @trusted { 709 static if (distinct) { 710 auto sql_base = " 711 SELECT count(*) 712 FROM ( 713 SELECT count(*) 714 FROM %s t0, %s t1,%s t4%s 715 WHERE 716 %s 717 t0.st_id = t1.id AND 718 t0.st_id = t4.st_id AND 719 t4.nomut != 0 AND 720 t1.status IN (%(%s,%)) AND 721 t0.kind IN (%(%s,%)) 722 GROUP BY t1.id)"; 723 } else { 724 auto sql_base = " 725 SELECT count(*) 726 FROM %s t0, %s t1,%s t4%s 727 WHERE 728 %s 729 t0.st_id = t1.id AND 730 t0.st_id = t4.st_id AND 731 t4.nomut != 0 AND 732 t1.status IN (%(%s,%)) AND 733 t0.kind IN (%(%s,%))"; 734 } 735 const query = () { 736 auto fq = file.length == 0 737 ? null : "t0.mp_id = t2.id AND t2.file_id = t3.id AND t3.path = :path AND"; 738 auto fq_from = file.length == 0 ? null : format(", %s t2, %s t3", 739 mutationPointTable, filesTable); 740 return format(sql_base, mutationTable, mutationStatusTable, 741 srcMetadataTable, fq_from, fq, status, kinds.map!(a => cast(int) a)); 742 }(); 743 744 typeof(return) rval; 745 auto stmt = db.prepare(query); 746 if (file.length != 0) 747 stmt.bind(":path", file); 748 auto res = stmt.execute; 749 if (!res.empty) 750 rval = MetadataNoMutEntry(res.front.peek!long(0)); 751 return rval; 752 } 753 754 /// ditto. 755 alias aliveNoMutSrcMutants = countNoMutMutants!([Mutation.Status.alive], true); 756 757 /// Returns: mutants killed by the test case. 758 MutationStatusId[] testCaseKilledSrcMutants(const Mutation.Kind[] kinds, TestCase tc) @trusted { 759 const query = format(" 760 SELECT t1.id 761 FROM %s t0, %s t1, %s t2, %s t3 762 WHERE 763 t0.st_id = t1.id AND 764 t1.status = :st AND 765 t0.kind IN (%(%s,%)) AND 766 t2.name = :name AND 767 t2.id = t3.tc_id AND 768 t3.st_id = t1.id 769 GROUP BY t1.id", mutationTable, mutationStatusTable, 770 allTestCaseTable, killedTestCaseTable, kinds.map!(a => cast(int) a)); 771 772 auto stmt = db.prepare(query); 773 stmt.bind(":st", cast(long) Mutation.Status.killed); 774 stmt.bind(":name", tc.name); 775 776 auto app = appender!(MutationStatusId[])(); 777 foreach (res; stmt.execute) 778 app.put(MutationStatusId(res.peek!long(0))); 779 780 return app.data; 781 } 782 783 /// Returns: mutants at mutations points that the test case has killed mutants at. 784 alias testCaseMutationPointAliveSrcMutants = testCaseCountSrcMutants!([ 785 Mutation.Status.alive 786 ]); 787 /// ditto 788 alias testCaseMutationPointTimeoutSrcMutants = testCaseCountSrcMutants!( 789 [Mutation.Status.timeout]); 790 /// ditto 791 alias testCaseMutationPointKilledSrcMutants = testCaseCountSrcMutants!([ 792 Mutation.Status.killed 793 ]); 794 /// ditto 795 alias testCaseMutationPointUnknownSrcMutants = testCaseCountSrcMutants!( 796 [Mutation.Status.unknown]); 797 /// ditto 798 alias testCaseMutationPointKilledByCompilerSrcMutants = testCaseCountSrcMutants!( 799 [Mutation.Status.killedByCompiler]); 800 /// ditto 801 alias testCaseMutationPointTotalSrcMutants = testCaseCountSrcMutants!( 802 [ 803 Mutation.Status.alive, Mutation.Status.killed, Mutation.Status.timeout 804 ]); 805 806 private MutationStatusId[] testCaseCountSrcMutants(int[] status)( 807 const Mutation.Kind[] kinds, TestCase tc) @trusted { 808 const query = format(" 809 SELECT t1.id 810 FROM %s t0, %s t1 811 WHERE 812 t0.mp_id IN (SELECT t1.id 813 FROM %s t0,%s t1, %s t2, %s t3 814 WHERE 815 t0.mp_id = t1.id AND 816 t2.name = :name AND 817 t2.id = t3.tc_id AND 818 t3.st_id = t0.st_id 819 ) 820 AND 821 t0.st_id = t1.id AND 822 t1.status IN (%(%s,%)) AND 823 t0.kind IN (%(%s,%)) 824 GROUP BY t1.id", mutationTable, mutationStatusTable, mutationTable, mutationPointTable, 825 allTestCaseTable, killedTestCaseTable, status, kinds.map!(a => cast(int) a)); 826 827 auto stmt = db.prepare(query); 828 stmt.bind(":name", tc.name); 829 830 auto app = appender!(MutationStatusId[])(); 831 foreach (res; stmt.execute) 832 app.put(MutationStatusId(res.peek!long(0))); 833 834 return app.data; 835 } 836 837 void put(const Path p, Checksum cs, const Language lang) @trusted { 838 enum sql = format("INSERT OR IGNORE INTO %s (path, checksum0, checksum1, lang) VALUES (:path, :checksum0, :checksum1, :lang)", 839 filesTable); 840 auto stmt = db.prepare(sql); 841 stmt.bind(":path", cast(string) p); 842 stmt.bind(":checksum0", cast(long) cs.c0); 843 stmt.bind(":checksum1", cast(long) cs.c1); 844 stmt.bind(":lang", cast(long) lang); 845 stmt.execute; 846 } 847 848 /** Save line metadata to the database which is used to associate line 849 * metadata with mutants. 850 */ 851 void put(const LineMetadata[] mdata) { 852 import sumtype; 853 854 // TODO: convert to microrm 855 enum sql = format("INSERT OR IGNORE INTO %s 856 (file_id, line, nomut, tag, comment) 857 VALUES(:fid, :line, :nomut, :tag, :comment)", 858 rawSrcMetadataTable); 859 860 auto stmt = db.prepare(sql); 861 foreach (meta; mdata) { 862 auto nomut = meta.attr.match!((NoMetadata a) => NoMut.init, (NoMut a) => a); 863 stmt.bindAll(cast(long) meta.id, meta.line, meta.isNoMut, nomut.tag, nomut.comment); 864 stmt.execute; 865 stmt.reset; 866 } 867 } 868 869 /// Store all found mutants. 870 void put(MutationPointEntry2[] mps, AbsolutePath rel_dir) @trusted { 871 enum insert_mp_sql = format("INSERT OR IGNORE INTO %s 872 (file_id, offset_begin, offset_end, line, column, line_end, column_end) 873 SELECT id,:begin,:end,:line,:column,:line_end,:column_end 874 FROM %s 875 WHERE 876 path = :path", mutationPointTable, filesTable); 877 auto mp_stmt = db.prepare(insert_mp_sql); 878 879 foreach (mp; mps) { 880 auto rel_file = relativePath(mp.file, rel_dir).Path; 881 mp_stmt.bind(":begin", mp.offset.begin); 882 mp_stmt.bind(":end", mp.offset.end); 883 mp_stmt.bind(":line", mp.sloc.line); 884 mp_stmt.bind(":column", mp.sloc.column); 885 mp_stmt.bind(":line_end", mp.slocEnd.line); 886 mp_stmt.bind(":column_end", mp.slocEnd.column); 887 mp_stmt.bind(":path", cast(string) rel_file); 888 mp_stmt.execute; 889 mp_stmt.reset; 890 } 891 892 enum insert_cmut_sql = format("INSERT OR IGNORE INTO %s 893 (status,test_cnt,update_ts,added_ts,checksum0,checksum1) 894 VALUES(:st,0,:update_ts,:added_ts,:c0,:c1)", 895 mutationStatusTable); 896 auto cmut_stmt = db.prepare(insert_cmut_sql); 897 const ts = Clock.currTime.toUTC.toSqliteDateTime; 898 cmut_stmt.bind(":st", Mutation.Status.unknown); 899 cmut_stmt.bind(":update_ts", ts); 900 cmut_stmt.bind(":added_ts", ts); 901 foreach (cm; mps.map!(a => a.cms).joiner) { 902 cmut_stmt.bind(":c0", cast(long) cm.id.c0); 903 cmut_stmt.bind(":c1", cast(long) cm.id.c1); 904 cmut_stmt.execute; 905 cmut_stmt.reset; 906 } 907 908 enum insert_m_sql = format("INSERT OR IGNORE INTO %s 909 (mp_id, st_id, kind) 910 SELECT t0.id,t1.id,:kind FROM %s t0, %s t1, %s t2 WHERE 911 t2.path = :path AND 912 t0.file_id = t2.id AND 913 t0.offset_begin = :off_begin AND 914 t0.offset_end = :off_end AND 915 t1.checksum0 = :c0 AND 916 t1.checksum1 = :c1", mutationTable, 917 mutationPointTable, mutationStatusTable, filesTable); 918 auto insert_m = db.prepare(insert_m_sql); 919 920 foreach (mp; mps) { 921 foreach (m; mp.cms) { 922 auto rel_file = relativePath(mp.file, rel_dir).Path; 923 insert_m.bind(":path", cast(string) rel_file); 924 insert_m.bind(":off_begin", mp.offset.begin); 925 insert_m.bind(":off_end", mp.offset.end); 926 insert_m.bind(":c0", cast(long) m.id.c0); 927 insert_m.bind(":c1", cast(long) m.id.c1); 928 insert_m.bind(":kind", m.mut.kind); 929 insert_m.execute; 930 insert_m.reset; 931 } 932 } 933 } 934 935 /** Remove all mutants points from the database. 936 * 937 * This removes all the mutants because of the cascade delete of the 938 * tables. But it will keep the mutation statuses and thus the checksums 939 * and the status of the code changes. 940 * 941 * This then mean that when mutations+mutation points are added back they 942 * may reconnect with a mutation status. 943 */ 944 void removeAllMutationPoints() @trusted { 945 enum sql = format!"DELETE FROM %s"(mutationPointTable); 946 db.run(sql); 947 } 948 949 /// ditto 950 void removeAllFiles() @trusted { 951 enum sql = format!"DELETE FROM %s"(filesTable); 952 db.run(sql); 953 } 954 955 /// Remove mutants that have no connection to a mutation point, orphened mutants. 956 void removeOrphanedMutants() @trusted { 957 enum sql = format!"DELETE FROM %s WHERE id NOT IN (SELECT st_id FROM %s)"( 958 mutationStatusTable, mutationTable); 959 db.run(sql); 960 } 961 962 /** Add a link between the mutation and what test case killed it. 963 * 964 * Params: 965 * id = ? 966 * tcs = test cases to add 967 */ 968 void updateMutationTestCases(const MutationId id, const(TestCase)[] tcs) @trusted { 969 if (tcs.length == 0) 970 return; 971 972 immutable st_id = () { 973 enum st_id_for_mutation_q = format("SELECT st_id FROM %s WHERE id=:id", mutationTable); 974 auto stmt = db.prepare(st_id_for_mutation_q); 975 stmt.bind(":id", cast(long) id); 976 return stmt.execute.oneValue!long; 977 }(); 978 updateMutationTestCases(MutationStatusId(st_id), tcs); 979 } 980 981 /** Add a link between the mutation and what test case killed it. 982 * 983 * Params: 984 * id = ? 985 * tcs = test cases to add 986 */ 987 void updateMutationTestCases(const MutationStatusId st_id, const(TestCase)[] tcs) @trusted { 988 if (tcs.length == 0) 989 return; 990 991 try { 992 enum remove_old_sql = format("DELETE FROM %s WHERE st_id=:id", killedTestCaseTable); 993 auto stmt = db.prepare(remove_old_sql); 994 stmt.bind(":id", cast(ulong) st_id); 995 stmt.execute; 996 } catch (Exception e) { 997 } 998 999 enum add_if_non_exist_tc_sql = format( 1000 "INSERT INTO %s (name) SELECT :name1 WHERE NOT EXISTS (SELECT * FROM %s WHERE name = :name2)", 1001 allTestCaseTable, allTestCaseTable); 1002 auto stmt_insert_tc = db.prepare(add_if_non_exist_tc_sql); 1003 1004 enum add_new_sql = format( 1005 "INSERT INTO %s (st_id, tc_id, location) SELECT :st_id,t1.id,:loc FROM %s t1 WHERE t1.name = :tc", 1006 killedTestCaseTable, allTestCaseTable); 1007 auto stmt_insert = db.prepare(add_new_sql); 1008 foreach (const tc; tcs) { 1009 try { 1010 stmt_insert_tc.reset; 1011 stmt_insert_tc.bind(":name1", tc.name); 1012 stmt_insert_tc.bind(":name2", tc.name); 1013 stmt_insert_tc.execute; 1014 1015 stmt_insert.reset; 1016 stmt_insert.bind(":st_id", cast(ulong) st_id); 1017 stmt_insert.bind(":loc", tc.location); 1018 stmt_insert.bind(":tc", tc.name); 1019 stmt_insert.execute; 1020 } catch (Exception e) { 1021 logger.warning(e.msg); 1022 } 1023 } 1024 } 1025 1026 /** Set detected test cases. 1027 * 1028 * This will replace those that where previously stored. 1029 * 1030 * Returns: ID of affected mutation statuses. 1031 */ 1032 MutationStatusId[] setDetectedTestCases(const(TestCase)[] tcs) @trusted { 1033 if (tcs.length == 0) 1034 return null; 1035 1036 auto mut_status_ids = appender!(MutationStatusId[])(); 1037 1038 immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 1039 internalAddDetectedTestCases(tcs, tmp_name); 1040 1041 immutable mut_st_id = format!"SELECT DISTINCT t1.st_id 1042 FROM %s t0, %s t1 1043 WHERE 1044 t0.name NOT IN (SELECT name FROM %s) AND 1045 t0.id = t1.tc_id"(allTestCaseTable, 1046 killedTestCaseTable, tmp_name); 1047 foreach (res; db.prepare(mut_st_id).execute) 1048 mut_status_ids.put(res.peek!long(0).MutationStatusId); 1049 1050 immutable remove_old_sql = format!"DELETE FROM %s WHERE name NOT IN (SELECT name FROM %s)"( 1051 allTestCaseTable, tmp_name); 1052 db.run(remove_old_sql); 1053 1054 db.run(format!"DROP TABLE %s"(tmp_name)); 1055 1056 return mut_status_ids.data; 1057 } 1058 1059 /** Add test cases to those that have been detected. 1060 * 1061 * They will be added if they are unique. 1062 */ 1063 void addDetectedTestCases(const(TestCase)[] tcs) @trusted { 1064 if (tcs.length == 0) 1065 return; 1066 1067 immutable tmp_name = "tmp_new_tc_" ~ __LINE__.to!string; 1068 internalAddDetectedTestCases(tcs, tmp_name); 1069 db.run(format!"DROP TABLE %s"(tmp_name)); 1070 } 1071 1072 /// ditto. 1073 private void internalAddDetectedTestCases(const(TestCase)[] tcs, string tmp_tbl) @trusted { 1074 db.run(format!"CREATE TEMP TABLE %s (id INTEGER PRIMARY KEY, name TEXT NOT NULL)"( 1075 tmp_tbl)); 1076 1077 immutable add_tc_sql = format!"INSERT INTO %s (name) VALUES(:name)"(tmp_tbl); 1078 auto insert_s = db.prepare(add_tc_sql); 1079 foreach (tc; tcs) { 1080 insert_s.bind(":name", tc.name); 1081 insert_s.execute; 1082 insert_s.reset; 1083 } 1084 1085 // https://stackoverflow.com/questions/2686254/how-to-select-all-records-from-one-table-that-do-not-exist-in-another-table 1086 //Q: What is happening here? 1087 // 1088 //A: Conceptually, we select all rows from table1 and for each row we 1089 //attempt to find a row in table2 with the same value for the name 1090 //column. If there is no such row, we just leave the table2 portion of 1091 //our result empty for that row. Then we constrain our selection by 1092 //picking only those rows in the result where the matching row does not 1093 //exist. Finally, We ignore all fields from our result except for the 1094 //name column (the one we are sure that exists, from table1). 1095 // 1096 //While it may not be the most performant method possible in all cases, 1097 //it should work in basically every database engine ever that attempts 1098 //to implement ANSI 92 SQL 1099 immutable add_missing_sql = format!"INSERT INTO %s (name) SELECT t1.name FROM %s t1 LEFT JOIN %s t2 ON t2.name = t1.name WHERE t2.name IS NULL"( 1100 allTestCaseTable, tmp_tbl, allTestCaseTable); 1101 db.run(add_missing_sql); 1102 } 1103 1104 /// Returns: detected test cases. 1105 TestCase[] getDetectedTestCases() @trusted { 1106 auto rval = appender!(TestCase[])(); 1107 db.run(select!AllTestCaseTbl).map!(a => TestCase(a.name)).copy(rval); 1108 return rval.data; 1109 } 1110 1111 /// Returns: detected test cases. 1112 TestCaseId[] getDetectedTestCaseIds() @trusted { 1113 auto rval = appender!(TestCaseId[])(); 1114 db.run(select!AllTestCaseTbl).map!(a => TestCaseId(a.id)).copy(rval); 1115 return rval.data; 1116 } 1117 1118 /// Returns: test cases that has killed zero mutants. 1119 TestCase[] getTestCasesWithZeroKills() @trusted { 1120 enum sql = format("SELECT t1.name FROM %s t1 WHERE t1.id NOT IN (SELECT tc_id FROM %s)", 1121 allTestCaseTable, killedTestCaseTable); 1122 1123 auto rval = appender!(TestCase[])(); 1124 auto stmt = db.prepare(sql); 1125 foreach (a; stmt.execute) 1126 rval.put(TestCase(a.peek!string(0))); 1127 1128 return rval.data; 1129 } 1130 1131 /** Guarantees that the there are no duplications of `TestCaseId`. 1132 * 1133 * Returns: test cases that has killed at least one mutant. 1134 */ 1135 TestCaseId[] getTestCasesWithAtLeastOneKill(const Mutation.Kind[] kinds) @trusted { 1136 immutable sql = format!"SELECT DISTINCT t1.id 1137 FROM %s t1, %s t2, %s t3 1138 WHERE 1139 t1.id = t2.tc_id AND 1140 t2.st_id == t3.st_id AND 1141 t3.kind IN (%(%s,%))"(allTestCaseTable, 1142 killedTestCaseTable, mutationTable, kinds.map!(a => cast(int) a)); 1143 1144 auto rval = appender!(TestCaseId[])(); 1145 auto stmt = db.prepare(sql); 1146 foreach (a; stmt.execute) 1147 rval.put(TestCaseId(a.peek!long(0))); 1148 1149 return rval.data; 1150 } 1151 1152 /// Returns: the name of the test case. 1153 string getTestCaseName(const TestCaseId id) @trusted { 1154 enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 1155 auto stmt = db.prepare(sql); 1156 stmt.bind(":id", cast(long) id); 1157 auto res = stmt.execute; 1158 return res.oneValue!string; 1159 } 1160 1161 /// Returns: stats about the test case. 1162 Nullable!TestCaseInfo getTestCaseInfo(const TestCase tc, const Mutation.Kind[] kinds) @trusted { 1163 const sql = format("SELECT sum(t2.time),count(t1.st_id) 1164 FROM %s t0, %s t1, %s t2, %s t3 1165 WHERE 1166 t0.name = :name AND 1167 t0.id = t1.tc_id AND 1168 t1.st_id = t2.id AND 1169 t2.id = t3.st_id AND 1170 t3.kind IN (%(%s,%))", allTestCaseTable, 1171 killedTestCaseTable, mutationStatusTable, mutationTable, 1172 kinds.map!(a => cast(int) a)); 1173 auto stmt = db.prepare(sql); 1174 stmt.bind(":name", tc.name); 1175 1176 typeof(return) rval; 1177 foreach (a; stmt.execute) 1178 rval = TestCaseInfo(a.peek!long(0).dur!"msecs", a.peek!long(1)); 1179 return rval; 1180 } 1181 1182 /// Returns: all test cases for the file and the mutants they killed. 1183 TestCaseInfo2[] getAllTestCaseInfo2(const FileId file, const Mutation.Kind[] kinds) @trusted { 1184 // row of test case name and mutation id. 1185 const sql = format("SELECT t0.name,t3.id 1186 FROM %s t0, %s t1, %s t2, %s t3, %s t4 1187 WHERE 1188 t0.id = t1.tc_id AND 1189 t1.st_id = t2.id AND 1190 t2.id = t3.st_id AND 1191 t4.id = :file_id AND 1192 t3.kind IN (%(%s,%))", allTestCaseTable, killedTestCaseTable, 1193 mutationStatusTable, mutationTable, filesTable, kinds.map!(a => cast(int) a)); 1194 auto stmt = db.prepare(sql); 1195 stmt.bind(":file_id", cast(long) file); 1196 1197 MutationId[][string] data; 1198 foreach (row; stmt.execute) { 1199 const name = row.peek!string(0); 1200 if (auto v = name in data) { 1201 *v ~= MutationId(row.peek!long(1)); 1202 } else { 1203 data[name] = [MutationId(row.peek!long(1))]; 1204 } 1205 } 1206 1207 auto app = appender!(TestCaseInfo2[])(); 1208 data.byKeyValue.map!(a => TestCaseInfo2(TestCase(a.key), a.value)).copy(app); 1209 return app.data; 1210 } 1211 1212 /// Returns: the test case. 1213 Nullable!TestCase getTestCase(const TestCaseId id) @trusted { 1214 enum sql = format!"SELECT name FROM %s WHERE id = :id"(allTestCaseTable); 1215 auto stmt = db.prepare(sql); 1216 stmt.bind(":id", cast(long) id); 1217 1218 typeof(return) rval; 1219 foreach (res; stmt.execute) { 1220 rval = TestCase(res.peek!string(0)); 1221 } 1222 return rval; 1223 } 1224 1225 /// Returns: the test case id. 1226 Nullable!TestCaseId getTestCaseId(const TestCase tc) @trusted { 1227 enum sql = format!"SELECT id FROM %s WHERE name = :name"(allTestCaseTable); 1228 auto stmt = db.prepare(sql); 1229 stmt.bind(":name", tc.name); 1230 1231 typeof(return) rval; 1232 foreach (res; stmt.execute) { 1233 rval = TestCaseId(res.peek!long(0)); 1234 } 1235 return rval; 1236 } 1237 1238 /// The mutation ids are guaranteed to be sorted. 1239 /// Returns: the mutants the test case killed. 1240 MutationId[] getTestCaseMutantKills(const TestCaseId id, const Mutation.Kind[] kinds) @trusted { 1241 immutable sql = format!"SELECT t2.id 1242 FROM %s t1, %s t2 1243 WHERE 1244 t1.tc_id = :tid AND 1245 t1.st_id = t2.st_id AND 1246 t2.kind IN (%(%s,%)) 1247 ORDER BY 1248 t2.id"(killedTestCaseTable, 1249 mutationTable, kinds.map!(a => cast(int) a)); 1250 1251 auto rval = appender!(MutationId[])(); 1252 auto stmt = db.prepare(sql); 1253 stmt.bind(":tid", cast(long) id); 1254 foreach (a; stmt.execute) 1255 rval.put(MutationId(a.peek!long(0))); 1256 1257 return rval.data; 1258 } 1259 1260 /// Returns: test cases that killed the mutant. 1261 TestCase[] getTestCases(const MutationId id) @trusted { 1262 Appender!(TestCase[]) rval; 1263 1264 enum get_test_cases_sql = format!"SELECT t1.name,t2.location 1265 FROM %s t1, %s t2, %s t3 1266 WHERE 1267 t3.id = :id AND 1268 t3.st_id = t2.st_id AND 1269 t2.tc_id = t1.id"(allTestCaseTable, 1270 killedTestCaseTable, mutationTable); 1271 auto stmt = db.prepare(get_test_cases_sql); 1272 stmt.bind(":id", cast(long) id); 1273 foreach (a; stmt.execute) 1274 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 1275 1276 return rval.data; 1277 } 1278 1279 /** Returns: number of test cases 1280 */ 1281 long getNumOfTestCases() @trusted { 1282 enum num_test_cases_sql = format!"SELECT count(*) FROM %s"(allTestCaseTable); 1283 return db.execute(num_test_cases_sql).oneValue!long; 1284 } 1285 1286 /** Returns: test cases that killed other mutants at the same mutation point as `id`. 1287 */ 1288 TestCase[] getSurroundingTestCases(const MutationId id) @trusted { 1289 Appender!(TestCase[]) rval; 1290 1291 // TODO: optimize this. should be able to merge the two first queries to one. 1292 1293 // get the mutation point ID that id reside at 1294 long mp_id; 1295 { 1296 auto stmt = db.prepare(format!"SELECT mp_id FROM %s WHERE id=:id"(mutationTable)); 1297 stmt.bind(":id", cast(long) id); 1298 auto res = stmt.execute; 1299 if (res.empty) 1300 return null; 1301 mp_id = res.oneValue!long; 1302 } 1303 1304 // get all the mutation status ids at the mutation point 1305 long[] mut_st_ids; 1306 { 1307 auto stmt = db.prepare(format!"SELECT st_id FROM %s WHERE mp_id=:id"(mutationTable)); 1308 stmt.bind(":id", mp_id); 1309 auto res = stmt.execute; 1310 if (res.empty) 1311 return null; 1312 mut_st_ids = res.map!(a => a.peek!long(0)).array; 1313 } 1314 1315 // get all the test cases that are killed at the mutation point 1316 immutable get_test_cases_sql = format!"SELECT t2.name,t1.location FROM %s t1,%s t2 WHERE t1.tc_id == t2.id AND t0.st_id IN (%(%s,%))"( 1317 killedTestCaseTable, allTestCaseTable, mut_st_ids); 1318 auto stmt = db.prepare(get_test_cases_sql); 1319 foreach (a; stmt.execute) { 1320 rval.put(TestCase(a.peek!string(0), a.peek!string(1))); 1321 } 1322 1323 return rval.data; 1324 } 1325 1326 void removeTestCase(const Regex!char rex, const(Mutation.Kind)[] kinds) @trusted { 1327 immutable sql = format!"SELECT t1.id,t1.name FROM %s t1,%s t2, %s t3 WHERE t1.id = t2.tc_id AND t2.st_id = t3.st_id AND t3.kind IN (%(%s,%))"( 1328 allTestCaseTable, killedTestCaseTable, mutationTable, 1329 kinds.map!(a => cast(long) a)); 1330 auto stmt = db.prepare(sql); 1331 1332 auto del_stmt = db.prepare(format!"DELETE FROM %s WHERE id=:id"(allTestCaseTable)); 1333 foreach (row; stmt.execute) { 1334 string tc = row.peek!string(1); 1335 if (tc.matchFirst(rex).empty) 1336 continue; 1337 1338 long id = row.peek!long(0); 1339 del_stmt.reset; 1340 del_stmt.bind(":id", id); 1341 del_stmt.execute; 1342 } 1343 } 1344 1345 /// Returns: the context for the timeout algorithm. 1346 MutantTimeoutCtx getMutantTimeoutCtx() @trusted { 1347 foreach (res; db.run(select!MutantTimeoutCtx)) 1348 return res; 1349 return MutantTimeoutCtx.init; 1350 } 1351 1352 void putMutantTimeoutCtx(const MutantTimeoutCtx ctx) @trusted { 1353 db.run(delete_!MutantTimeoutCtx); 1354 db.run(insert!MutantTimeoutCtx.insert, ctx); 1355 } 1356 1357 void putMutantInTimeoutWorklist(const MutationStatusId id) @trusted { 1358 const sql = format!"INSERT OR IGNORE INTO %s (id) VALUES (:id)"(mutantTimeoutWorklistTable); 1359 auto stmt = db.prepare(sql); 1360 stmt.bind(":id", cast(ulong) id); 1361 stmt.execute; 1362 } 1363 1364 /** Remove all mutants that are in the worklist that do NOT have the 1365 * mutation status timeout. 1366 */ 1367 void reduceMutantTimeoutWorklist() @trusted { 1368 immutable sql = format!"DELETE FROM %1$s 1369 WHERE 1370 id IN (SELECT id FROM %2$s WHERE status != :status)"( 1371 mutantTimeoutWorklistTable, mutationStatusTable); 1372 auto stmt = db.prepare(sql); 1373 stmt.bind(":status", cast(ubyte) Mutation.Status.timeout); 1374 stmt.execute; 1375 } 1376 1377 /// Remove all mutants from the worklist. 1378 void clearMutantTimeoutWorklist() @trusted { 1379 immutable sql = format!"DELETE FROM %1$s"(mutantTimeoutWorklistTable); 1380 db.run(sql); 1381 } 1382 1383 /// Returns: the number of mutants in the worklist. 1384 long countMutantTimeoutWorklist() @trusted { 1385 immutable sql = format!"SELECT count(*) FROM %1$s"(mutantTimeoutWorklistTable); 1386 auto stmt = db.prepare(sql); 1387 auto res = stmt.execute(); 1388 return res.oneValue!long; 1389 } 1390 1391 /// Changes the status of mutants in the timeout worklist to unknown. 1392 void resetMutantTimeoutWorklist() @trusted { 1393 immutable sql = format!"UPDATE %1$s SET status=:st WHERE id IN (SELECT id FROM %2$s)"( 1394 mutationStatusTable, mutantTimeoutWorklistTable); 1395 auto stmt = db.prepare(sql); 1396 stmt.bind(":st", cast(ubyte) Mutation.Status.unknown); 1397 stmt.execute; 1398 } 1399 }