1 /** 2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved. 3 License: MPL-2 4 Author: Joakim Brännström (joakim.brannstrom@gmx.com) 5 6 This Source Code Form is subject to the terms of the Mozilla Public License, 7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain 8 one at http://mozilla.org/MPL/2.0/. 9 */ 10 module dextool.plugin.mutate.backend.test_mutant; 11 12 import core.time : Duration, dur; 13 import logger = std.experimental.logger; 14 import std.algorithm : map; 15 import std.array : empty, array, appender; 16 import std.datetime : SysTime, Clock; 17 import std.datetime.stopwatch : StopWatch, AutoStart; 18 import std.exception : collectException; 19 import std.format : format; 20 import std.random : randomCover; 21 import std.typecons : Nullable, Tuple, Yes; 22 23 import blob_model : Blob; 24 import my.container.vector; 25 import my.fsm : Fsm, next, act, get, TypeDataMap; 26 import my.named_type; 27 import my.optional; 28 import my.set; 29 import proc : DrainElement; 30 import sumtype; 31 static import my.fsm; 32 33 import dextool.plugin.mutate.backend.database : Database, MutationEntry, 34 NextMutationEntry, spinSql, TestFile; 35 import dextool.plugin.mutate.backend.interface_ : FilesysIO; 36 import dextool.plugin.mutate.backend.test_mutant.common; 37 import dextool.plugin.mutate.backend.test_mutant.test_cmd_runner : TestRunner, 38 findExecutables, TestRunResult = TestResult; 39 import dextool.plugin.mutate.backend.type : Mutation, TestCase, ExitStatus; 40 import dextool.plugin.mutate.config; 41 import dextool.plugin.mutate.type : ShellCommand; 42 import dextool.type : AbsolutePath, ExitStatusType, Path; 43 44 @safe: 45 46 auto makeTestMutant() { 47 return BuildTestMutant(); 48 } 49 50 private: 51 52 struct BuildTestMutant { 53 @safe: 54 55 import dextool.plugin.mutate.type : MutationKind; 56 57 private struct InternalData { 58 Mutation.Kind[] kinds; 59 FilesysIO filesys_io; 60 ConfigMutationTest config; 61 } 62 63 private InternalData data; 64 65 auto config(ConfigMutationTest c) @trusted nothrow { 66 data.config = c; 67 return this; 68 } 69 70 auto mutations(MutationKind[] v) nothrow { 71 import dextool.plugin.mutate.backend.mutation_type : toInternal; 72 73 logger.infof("mutation operators: %(%s, %)", v).collectException; 74 data.kinds = toInternal(v); 75 return this; 76 } 77 78 ExitStatusType run(const AbsolutePath dbPath, FilesysIO fio) @trusted { 79 try { 80 auto db = Database.make(dbPath); 81 return internalRun(db, fio); 82 } catch (Exception e) { 83 logger.error(e.msg).collectException; 84 } 85 86 return ExitStatusType.Errors; 87 } 88 89 private ExitStatusType internalRun(ref Database db, FilesysIO fio) { 90 // trusted because the lifetime of the database is guaranteed to outlive any instances in this scope 91 auto db_ref = () @trusted { return &db; }(); 92 93 auto cleanup = new AutoCleanup; 94 scope (exit) 95 cleanup.cleanup; 96 97 auto driver_data = DriverData(db_ref, fio, data.kinds, cleanup, data.config); 98 99 auto test_driver = TestDriver(driver_data); 100 101 while (test_driver.isRunning) { 102 test_driver.execute; 103 } 104 105 return test_driver.status; 106 } 107 } 108 109 struct DriverData { 110 Database* db; 111 FilesysIO filesysIO; 112 Mutation.Kind[] kinds; 113 AutoCleanup autoCleanup; 114 ConfigMutationTest conf; 115 } 116 117 struct MeasureTestDurationResult { 118 bool ok; 119 Duration[] runtime; 120 } 121 122 /** Measure the time it takes to run the test command. 123 * 124 * The runtime is the lowest of three executions. Anything else is assumed to 125 * be variations in the system. 126 * 127 * If the tests fail (exit code isn't 0) any time then they are too unreliable 128 * to use for mutation testing. 129 * 130 * Params: 131 * runner = ? 132 * samples = number of times to run the test suite 133 */ 134 MeasureTestDurationResult measureTestCommand(ref TestRunner runner, int samples) @safe nothrow { 135 import std.algorithm : min; 136 import proc; 137 138 if (runner.empty) { 139 collectException(logger.error("No test command(s) specified (--test-cmd)")); 140 return MeasureTestDurationResult(false); 141 } 142 143 static struct Rval { 144 TestRunResult result; 145 Duration runtime; 146 } 147 148 auto runTest() @safe { 149 auto sw = StopWatch(AutoStart.yes); 150 auto res = runner.run; 151 return Rval(res, sw.peek); 152 } 153 154 static void print(DrainElement[] data) @trusted { 155 import std.stdio : stdout, write; 156 157 foreach (l; data) { 158 write(l.byUTF8); 159 } 160 stdout.flush; 161 } 162 163 Duration[] runtimes; 164 bool failed; 165 for (int i; i < samples && !failed; ++i) { 166 try { 167 auto res = runTest; 168 final switch (res.result.status) with (TestRunResult) { 169 case Status.passed: 170 runtimes ~= res.runtime; 171 break; 172 case Status.failed: 173 goto case; 174 case Status.timeout: 175 goto case; 176 case Status.error: 177 failed = true; 178 print(res.result.output); 179 break; 180 } 181 logger.infof("%s: Measured test command runtime %s", i, res.runtime); 182 } catch (Exception e) { 183 logger.error(e.msg).collectException; 184 failed = true; 185 } 186 } 187 188 return MeasureTestDurationResult(!failed, runtimes); 189 } 190 191 struct TestDriver { 192 import std.datetime : SysTime; 193 import dextool.plugin.mutate.backend.database : SchemataId, MutationStatusId; 194 import dextool.plugin.mutate.backend.report.analyzers : EstimateScore; 195 import dextool.plugin.mutate.backend.test_mutant.source_mutant : MutationTestDriver; 196 import dextool.plugin.mutate.backend.test_mutant.timeout : calculateTimeout, TimeoutFsm; 197 import dextool.plugin.mutate.type : MutationOrder; 198 199 /// Runs the test commands. 200 TestRunner runner; 201 202 /// 203 TestCaseAnalyzer testCaseAnalyzer; 204 205 /// Stop conditions (most of them) 206 TestStopCheck stopCheck; 207 208 static struct Global { 209 DriverData data; 210 211 TimeoutFsm timeoutFsm; 212 213 /// The time it takes to execute the test suite when no mutant is injected. 214 Duration testSuiteRuntime; 215 216 /// the next mutant to test, if there are any. 217 MutationEntry nextMutant; 218 219 // when the user manually configure the timeout it means that the 220 // timeout algorithm should not be used. 221 bool hardcodedTimeout; 222 223 /// Test commands to execute. 224 ShellCommand[] testCmds; 225 226 /// mutation score estimation 227 EstimateScore estimate; 228 229 // The order to test mutants. It is either affected by the user directly or if pull request mode is activated. 230 MutationOrder mutationOrder; 231 } 232 233 static struct UpdateTimeoutData { 234 long lastTimeoutIter; 235 } 236 237 static struct None { 238 } 239 240 static struct Initialize { 241 bool halt; 242 } 243 244 static struct PullRequest { 245 } 246 247 static struct PullRequestData { 248 import dextool.plugin.mutate.type : TestConstraint; 249 250 TestConstraint constraint; 251 } 252 253 static struct SanityCheck { 254 bool sanityCheckFailed; 255 } 256 257 static struct AnalyzeTestCmdForTestCase { 258 TestCase[] foundTestCases; 259 } 260 261 static struct UpdateAndResetAliveMutants { 262 TestCase[] foundTestCases; 263 } 264 265 static struct ResetOldMutant { 266 } 267 268 static struct ResetOldMutantData { 269 /// Number of mutants that where reset. 270 long maxReset; 271 NamedType!(double, Tag!"OldMutantPercentage", double.init, TagStringable) resetPercentage; 272 } 273 274 static struct Cleanup { 275 } 276 277 static struct CheckMutantsLeft { 278 bool allMutantsTested; 279 } 280 281 static struct SaveMutationScore { 282 } 283 284 static struct ParseStdin { 285 } 286 287 static struct PreCompileSut { 288 bool compilationError; 289 } 290 291 static struct FindTestCmds { 292 } 293 294 static struct ChooseMode { 295 } 296 297 static struct MeasureTestSuite { 298 bool unreliableTestSuite; 299 } 300 301 static struct MutationTest { 302 NamedType!(bool, Tag!"MutationError", bool.init, TagStringable) mutationError; 303 MutationTestResult[] result; 304 } 305 306 static struct CheckTimeout { 307 bool timeoutUnchanged; 308 } 309 310 static struct NextSchemataData { 311 SchemataId[] schematas; 312 long totalSchematas; 313 long invalidSchematas; 314 } 315 316 static struct NextSchemata { 317 NamedType!(bool, Tag!"HasSchema", bool.init, TagStringable, ImplicitConvertable) hasSchema; 318 SchemataId schemataId; 319 320 /// stop mutation testing because the last schema has been used and the 321 /// user has configured that the testing should stop now. 322 NamedType!(bool, Tag!"StopTesting", bool.init, TagStringable, ImplicitConvertable) stop; 323 } 324 325 static struct SchemataTest { 326 SchemataId id; 327 MutationTestResult[] result; 328 bool fatalError; 329 } 330 331 static struct SchemataPruneUsed { 332 } 333 334 static struct Done { 335 } 336 337 static struct Error { 338 } 339 340 static struct UpdateTimeout { 341 } 342 343 static struct CheckPullRequestMutant { 344 NamedType!(bool, Tag!"NoUnknown", bool.init, TagStringable, ImplicitConvertable) noUnknownMutantsLeft; 345 } 346 347 static struct CheckPullRequestMutantData { 348 long startWorklistCnt; 349 long stopAfter; 350 } 351 352 static struct NextMutant { 353 NamedType!(bool, Tag!"NoUnknown", bool.init, TagStringable, ImplicitConvertable) noUnknownMutantsLeft; 354 } 355 356 static struct HandleTestResult { 357 MutationTestResult[] result; 358 } 359 360 static struct CheckStopCond { 361 bool halt; 362 } 363 364 static struct LoadSchematas { 365 } 366 367 static struct OverloadCheck { 368 bool sleep; 369 } 370 371 static struct ContinuesCheckTestSuite { 372 bool ok; 373 } 374 375 static struct ContinuesCheckTestSuiteData { 376 long lastWorklistCnt; 377 SysTime lastCheck; 378 } 379 380 static struct Stop { 381 } 382 383 static struct Coverage { 384 bool propagate; 385 bool fatalError; 386 } 387 388 static struct PropagateCoverage { 389 } 390 391 alias Fsm = my.fsm.Fsm!(None, Initialize, SanityCheck, 392 AnalyzeTestCmdForTestCase, UpdateAndResetAliveMutants, ResetOldMutant, 393 Cleanup, CheckMutantsLeft, PreCompileSut, MeasureTestSuite, NextMutant, 394 MutationTest, HandleTestResult, CheckTimeout, Done, Error, 395 UpdateTimeout, CheckStopCond, PullRequest, CheckPullRequestMutant, ParseStdin, 396 FindTestCmds, ChooseMode, NextSchemata, SchemataTest, LoadSchematas, 397 SchemataPruneUsed, Stop, SaveMutationScore, OverloadCheck, 398 Coverage, PropagateCoverage, ContinuesCheckTestSuite); 399 alias LocalStateDataT = Tuple!(UpdateTimeoutData, CheckPullRequestMutantData, 400 PullRequestData, ResetOldMutantData, NextSchemataData, ContinuesCheckTestSuiteData); 401 402 private { 403 Fsm fsm; 404 Global global; 405 TypeDataMap!(LocalStateDataT, UpdateTimeout, CheckPullRequestMutant, 406 PullRequest, ResetOldMutant, NextSchemata, ContinuesCheckTestSuite) local; 407 bool isRunning_ = true; 408 bool isDone = false; 409 } 410 411 this(DriverData data) { 412 this.global = Global(data); 413 this.global.timeoutFsm = TimeoutFsm(data.kinds); 414 this.global.hardcodedTimeout = !global.data.conf.mutationTesterRuntime.isNull; 415 local.get!PullRequest.constraint = global.data.conf.constraint; 416 local.get!ResetOldMutant.maxReset = global.data.conf.oldMutantsNr; 417 local.get!ResetOldMutant.resetPercentage = global.data.conf.oldMutantPercentage; 418 this.global.testCmds = global.data.conf.mutationTester; 419 this.global.mutationOrder = global.data.conf.mutationOrder; 420 421 this.runner.useEarlyStop(global.data.conf.useEarlyTestCmdStop); 422 this.runner = TestRunner.make(global.data.conf.testPoolSize); 423 this.runner.useEarlyStop(global.data.conf.useEarlyTestCmdStop); 424 // using an unreasonable timeout to make it possible to analyze for 425 // test cases and measure the test suite. 426 this.runner.timeout = 999.dur!"hours"; 427 this.runner.put(data.conf.mutationTester); 428 429 // TODO: allow a user, as is for test_cmd, to specify an array of 430 // external analyzers. 431 this.testCaseAnalyzer = TestCaseAnalyzer(global.data.conf.mutationTestCaseBuiltin, 432 global.data.conf.mutationTestCaseAnalyze, global.data.autoCleanup); 433 434 this.stopCheck = TestStopCheck(global.data.conf); 435 436 if (logger.globalLogLevel == logger.LogLevel.trace) 437 fsm.logger = (string s) { logger.trace(s); }; 438 } 439 440 static void execute_(ref TestDriver self) @trusted { 441 // see test_mutant/basis.md and figures/test_mutant_fsm.pu for a 442 // graphical view of the state machine. 443 444 self.fsm.next!((None a) => fsm(Initialize.init), (Initialize a) { 445 if (a.halt) 446 return fsm(CheckStopCond.init); 447 return fsm(SanityCheck.init); 448 }, (SanityCheck a) { 449 if (a.sanityCheckFailed) 450 return fsm(Error.init); 451 if (self.global.data.conf.unifiedDiffFromStdin) 452 return fsm(ParseStdin.init); 453 return fsm(PreCompileSut.init); 454 }, (ParseStdin a) => fsm(PreCompileSut.init), (AnalyzeTestCmdForTestCase a) => fsm( 455 UpdateAndResetAliveMutants(a.foundTestCases)), 456 (UpdateAndResetAliveMutants a) => fsm(CheckMutantsLeft.init), 457 (ResetOldMutant a) => fsm(UpdateTimeout.init), (Cleanup a) { 458 if (self.local.get!PullRequest.constraint.empty) 459 return fsm(NextSchemata.init); 460 return fsm(CheckPullRequestMutant.init); 461 }, (CheckMutantsLeft a) { 462 if (a.allMutantsTested 463 && self.global.data.conf.onOldMutants == ConfigMutationTest.OldMutant.nothing) 464 return fsm(Done.init); 465 return fsm(MeasureTestSuite.init); 466 }, (SaveMutationScore a) { return fsm(Stop.init); }, (PreCompileSut a) { 467 if (a.compilationError) 468 return fsm(Error.init); 469 if (self.global.data.conf.testCommandDir.empty) 470 return fsm(ChooseMode.init); 471 return fsm(FindTestCmds.init); 472 }, (FindTestCmds a) { return fsm(ChooseMode.init); }, (ChooseMode a) { 473 if (!self.local.get!PullRequest.constraint.empty) 474 return fsm(PullRequest.init); 475 if (!self.global.data.conf.mutationTestCaseAnalyze.empty 476 || !self.global.data.conf.mutationTestCaseBuiltin.empty) 477 return fsm(AnalyzeTestCmdForTestCase.init); 478 return fsm(CheckMutantsLeft.init); 479 }, (PullRequest a) => fsm(CheckMutantsLeft.init), (MeasureTestSuite a) { 480 if (a.unreliableTestSuite) 481 return fsm(Error.init); 482 if (self.global.data.conf.useCoverage) 483 return fsm(Coverage.init); 484 return fsm(LoadSchematas.init); 485 }, (Coverage a) { 486 if (a.fatalError) 487 return fsm(Error.init); 488 if (a.propagate) 489 return fsm(PropagateCoverage.init); 490 return fsm(LoadSchematas.init); 491 }, (PropagateCoverage a) => LoadSchematas.init, 492 (LoadSchematas a) => fsm(ResetOldMutant.init), (CheckPullRequestMutant a) { 493 if (a.noUnknownMutantsLeft) 494 return fsm(Done.init); 495 return fsm(NextMutant.init); 496 }, (NextSchemata a) { 497 if (a.hasSchema) 498 return fsm(SchemataTest(a.schemataId)); 499 if (a.stop) 500 return fsm(Done.init); 501 return fsm(NextMutant.init); 502 }, (SchemataTest a) { 503 if (a.fatalError) 504 return fsm(Error.init); 505 return fsm(CheckStopCond.init); 506 }, (NextMutant a) { 507 if (a.noUnknownMutantsLeft) 508 return fsm(CheckTimeout.init); 509 return fsm(MutationTest.init); 510 }, (UpdateTimeout a) => fsm(OverloadCheck.init), (OverloadCheck a) { 511 if (a.sleep) 512 return fsm(CheckStopCond.init); 513 return fsm(ContinuesCheckTestSuite.init); 514 }, (ContinuesCheckTestSuite a) { 515 if (a.ok) 516 return fsm(Cleanup.init); 517 return fsm(Error.init); 518 }, (MutationTest a) { 519 if (a.mutationError) 520 return fsm(Error.init); 521 return fsm(HandleTestResult(a.result)); 522 }, (HandleTestResult a) => fsm(CheckStopCond.init), (CheckStopCond a) { 523 if (a.halt) 524 return fsm(Done.init); 525 return fsm(UpdateTimeout.init); 526 }, (CheckTimeout a) { 527 if (a.timeoutUnchanged) 528 return fsm(Done.init); 529 return fsm(UpdateTimeout.init); 530 }, (SchemataPruneUsed a) => SaveMutationScore.init, 531 (Done a) => fsm(SchemataPruneUsed.init), 532 (Error a) => fsm(Stop.init), (Stop a) => fsm(a)); 533 534 self.fsm.act!(self); 535 } 536 537 nothrow: 538 void execute() { 539 try { 540 execute_(this); 541 } catch (Exception e) { 542 logger.warning(e.msg).collectException; 543 } 544 } 545 546 bool isRunning() { 547 return isRunning_; 548 } 549 550 ExitStatusType status() { 551 if (isDone) 552 return ExitStatusType.Ok; 553 return ExitStatusType.Errors; 554 } 555 556 void opCall(None data) { 557 } 558 559 void opCall(ref Initialize data) { 560 logger.info("Initializing worklist").collectException; 561 // need to use 10000 because in an untested code base it is not 562 // uncommon for mutants being in the thousands. 563 spinSql!(() { 564 global.data.db.updateWorklist(global.data.kinds, 565 Mutation.Status.unknown, 10000, global.mutationOrder); 566 }); 567 568 // detect if the system is overloaded before trying to do something 569 // slow such as compiling the SUT. 570 if (global.data.conf.loadBehavior == ConfigMutationTest.LoadBehavior.halt 571 && stopCheck.isHalt) { 572 data.halt = true; 573 } 574 } 575 576 void opCall(Stop data) { 577 isRunning_ = false; 578 } 579 580 void opCall(Done data) { 581 global.data.autoCleanup.cleanup; 582 logger.info("Done!").collectException; 583 isDone = true; 584 } 585 586 void opCall(Error data) { 587 global.data.autoCleanup.cleanup; 588 } 589 590 void opCall(ref SanityCheck data) { 591 import core.sys.posix.sys.stat : S_IWUSR; 592 import std.path : buildPath; 593 import my.file : getAttrs; 594 import colorlog : color, Color; 595 import dextool.plugin.mutate.backend.utility : checksum, Checksum; 596 597 logger.info("Sanity check of files to mutate").collectException; 598 599 auto failed = appender!(string[])(); 600 auto checksumFailed = appender!(string[])(); 601 auto writePermissionFailed = appender!(string[])(); 602 foreach (file; spinSql!(() { return global.data.db.getFiles; })) { 603 auto db_checksum = spinSql!(() { 604 return global.data.db.getFileChecksum(file); 605 }); 606 607 try { 608 auto abs_f = AbsolutePath(buildPath(global.data.filesysIO.getOutputDir, file)); 609 auto f_checksum = checksum(global.data.filesysIO.makeInput(abs_f).content[]); 610 if (db_checksum != f_checksum) { 611 checksumFailed.put(abs_f); 612 } 613 614 uint attrs; 615 if (getAttrs(abs_f, attrs)) { 616 if ((attrs & S_IWUSR) == 0) { 617 writePermissionFailed.put(abs_f); 618 } 619 } else { 620 writePermissionFailed.put(abs_f); 621 } 622 } catch (Exception e) { 623 failed.put(file); 624 logger.warningf("%s: %s", file, e.msg).collectException; 625 } 626 } 627 628 data.sanityCheckFailed = !failed.data.empty 629 || !checksumFailed.data.empty || !writePermissionFailed.data.empty; 630 631 if (data.sanityCheckFailed) { 632 logger.info(!failed.data.empty, 633 "Unknown error when checking the files").collectException; 634 foreach (f; failed.data) 635 logger.info(f).collectException; 636 637 logger.info(!checksumFailed.data.empty, 638 "Detected that file(s) has changed since last analyze where done") 639 .collectException; 640 logger.info(!checksumFailed.data.empty, 641 "Either restore the file(s) or rerun the analyze").collectException; 642 foreach (f; checksumFailed.data) 643 logger.info(f).collectException; 644 645 logger.info(!writePermissionFailed.data.empty, 646 "Files to mutate are not writable").collectException; 647 foreach (f; writePermissionFailed.data) 648 logger.info(f).collectException; 649 650 logger.info("Failed".color.fgRed).collectException; 651 } else { 652 logger.info("Ok".color.fgGreen).collectException; 653 } 654 } 655 656 void opCall(ref OverloadCheck data) { 657 if (global.data.conf.loadBehavior == ConfigMutationTest.LoadBehavior.slowdown 658 && stopCheck.isOverloaded) { 659 data.sleep = true; 660 logger.info(stopCheck.overloadToString).collectException; 661 stopCheck.pause; 662 } 663 } 664 665 void opCall(ref ContinuesCheckTestSuite data) { 666 import std.algorithm : max; 667 668 data.ok = true; 669 670 if (!global.data.conf.contCheckTestSuite) 671 return; 672 673 enum forceCheckEach = 1.dur!"hours"; 674 675 const wlist = spinSql!(() => global.data.db.getWorklistCount); 676 if (local.get!ContinuesCheckTestSuite.lastWorklistCnt == 0) { 677 // first time, just initialize. 678 local.get!ContinuesCheckTestSuite.lastWorklistCnt = wlist; 679 local.get!ContinuesCheckTestSuite.lastCheck = Clock.currTime + forceCheckEach; 680 return; 681 } 682 683 const period = global.data.conf.contCheckTestSuitePeriod.get; 684 const diffCnt = local.get!ContinuesCheckTestSuite.lastWorklistCnt - wlist; 685 if (!(wlist % period == 0 || diffCnt >= period 686 || Clock.currTime > local.get!ContinuesCheckTestSuite.lastCheck)) 687 return; 688 689 local.get!ContinuesCheckTestSuite.lastWorklistCnt = wlist; 690 local.get!ContinuesCheckTestSuite.lastCheck = Clock.currTime + forceCheckEach; 691 692 compile(global.data.conf.mutationCompile, global.data.conf.buildCmdTimeout, true).match!( 693 (Mutation.Status a) { data.ok = false; }, (bool success) { 694 data.ok = success; 695 }); 696 697 if (data.ok) { 698 try { 699 data.ok = measureTestCommand(runner, 1).ok; 700 } catch (Exception e) { 701 logger.error(e.msg).collectException; 702 data.ok = false; 703 } 704 } 705 706 if (!data.ok) { 707 logger.warning("Continues sanity check of the test suite has failed.").collectException; 708 logger.infof("Rolling back the status of the last %s mutants to status unknown.", 709 period).collectException; 710 foreach (a; spinSql!(() => global.data.db.getLatestMutants(global.data.kinds, 711 max(diffCnt, period)))) { 712 spinSql!(() => global.data.db.updateMutation(a.id, 713 Mutation.Status.unknown, ExitStatus(0), MutantTimeProfile.init)); 714 } 715 } 716 } 717 718 void opCall(ParseStdin data) { 719 import dextool.plugin.mutate.backend.diff_parser : diffFromStdin; 720 import dextool.plugin.mutate.type : Line; 721 722 try { 723 auto constraint = local.get!PullRequest.constraint; 724 foreach (pkv; diffFromStdin.toRange(global.data.filesysIO.getOutputDir)) { 725 constraint.value[pkv.key] ~= pkv.value.toRange.map!(a => Line(a)).array; 726 } 727 local.get!PullRequest.constraint = constraint; 728 } catch (Exception e) { 729 logger.warning(e.msg).collectException; 730 } 731 } 732 733 void opCall(ref AnalyzeTestCmdForTestCase data) { 734 import std.datetime.stopwatch : StopWatch; 735 import dextool.plugin.mutate.backend.type : TestCase; 736 737 TestCase[] found; 738 try { 739 auto res = runTester(runner); 740 auto analyze = testCaseAnalyzer.analyze(res.output, Yes.allFound); 741 742 analyze.match!((TestCaseAnalyzer.Success a) { found = a.found; }, 743 (TestCaseAnalyzer.Unstable a) { 744 logger.warningf("Unstable test cases found: [%-(%s, %)]", a.unstable); 745 found = a.found; 746 }, (TestCaseAnalyzer.Failed a) { 747 logger.warning("The parser that analyze the output for test case(s) failed"); 748 }); 749 found ~= global.testCmds.map!(a => TestCase(a.toShortString)).array; 750 } catch (Exception e) { 751 logger.warning(e.msg).collectException; 752 } 753 754 warnIfConflictingTestCaseIdentifiers(found); 755 data.foundTestCases = found; 756 } 757 758 void opCall(UpdateAndResetAliveMutants data) { 759 import std.traits : EnumMembers; 760 761 // the test cases before anything has potentially changed. 762 auto old_tcs = spinSql!(() { 763 Set!string old_tcs; 764 foreach (tc; global.data.db.getDetectedTestCases) { 765 old_tcs.add(tc.name); 766 } 767 return old_tcs; 768 }); 769 770 void transaction() @safe { 771 final switch (global.data.conf.onRemovedTestCases) with ( 772 ConfigMutationTest.RemovedTestCases) { 773 case doNothing: 774 global.data.db.addDetectedTestCases(data.foundTestCases); 775 break; 776 case remove: 777 bool update; 778 // change all mutants which, if a test case is removed, no 779 // longer has a test case that kills it to unknown status 780 foreach (id; global.data.db.setDetectedTestCases(data.foundTestCases)) { 781 if (!global.data.db.hasTestCases(id)) { 782 update = true; 783 global.data.db.updateMutationStatus(id, 784 Mutation.Status.unknown, ExitStatus(0)); 785 } 786 } 787 if (update) { 788 global.data.db.updateWorklist(global.data.kinds, Mutation.Status.unknown); 789 } 790 break; 791 } 792 } 793 794 auto found_tcs = spinSql!(() @trusted { 795 auto tr = global.data.db.transaction; 796 transaction(); 797 798 Set!string found_tcs; 799 foreach (tc; global.data.db.getDetectedTestCases) { 800 found_tcs.add(tc.name); 801 } 802 803 tr.commit; 804 return found_tcs; 805 }); 806 807 printDroppedTestCases(old_tcs, found_tcs); 808 809 if (hasNewTestCases(old_tcs, found_tcs) 810 && global.data.conf.onNewTestCases == ConfigMutationTest.NewTestCases.resetAlive) { 811 logger.info("Adding alive mutants to worklist").collectException; 812 spinSql!(() { 813 global.data.db.updateWorklist(global.data.kinds, Mutation.Status.alive); 814 // if these mutants are covered by the tests then they will be 815 // removed from the worklist in PropagateCoverage. 816 global.data.db.updateWorklist(global.data.kinds, Mutation.Status.noCoverage); 817 }); 818 } 819 } 820 821 void opCall(ResetOldMutant data) { 822 import std.range : enumerate; 823 import dextool.plugin.mutate.backend.database.type; 824 825 void printStatus(T0)(T0 oldestMutant, SysTime newestTest, SysTime newestFile) { 826 logger.info("Tests last changed ", newestTest).collectException; 827 logger.info("Source code last changed ", newestFile).collectException; 828 829 if (!oldestMutant.empty) { 830 logger.info("The oldest mutant is ", oldestMutant[0].updated).collectException; 831 } 832 } 833 834 if (global.data.conf.onOldMutants == ConfigMutationTest.OldMutant.nothing) { 835 return; 836 } 837 if (spinSql!(() { return global.data.db.getWorklistCount; }) != 0) { 838 // do not re-test any old mutants if there are still work to do in the worklist. 839 return; 840 } 841 842 const oldestMutant = spinSql!(() => global.data.db.getOldestMutants(global.data.kinds, 1)); 843 const newestTest = spinSql!(() => global.data.db.getNewestTestFile).orElse( 844 TestFile.init).timeStamp; 845 const newestFile = spinSql!(() => global.data.db.getNewestFile).orElse(SysTime.init); 846 if (!oldestMutant.empty && oldestMutant[0].updated > newestTest 847 && oldestMutant[0].updated > newestFile) { 848 // only re-test old mutants if needed. 849 logger.info("Mutation status is up to date").collectException; 850 printStatus(oldestMutant, newestTest, newestFile); 851 return; 852 } else { 853 logger.info("Mutation status is out of sync").collectException; 854 printStatus(oldestMutant, newestTest, newestFile); 855 } 856 857 const long testCnt = () { 858 if (local.get!ResetOldMutant.resetPercentage.get == 0.0) { 859 return local.get!ResetOldMutant.maxReset; 860 } 861 862 const total = spinSql!(() { 863 return global.data.db.totalSrcMutants(global.data.kinds).count; 864 }); 865 const rval = cast(long)(1 + total * local.get!ResetOldMutant.resetPercentage.get 866 / 100.0); 867 return rval; 868 }(); 869 870 auto oldest = spinSql!(() { 871 return global.data.db.getOldestMutants(global.data.kinds, testCnt); 872 }); 873 874 logger.infof("Adding %s old mutants to worklist", oldest.length).collectException; 875 spinSql!(() { 876 foreach (const old; oldest.enumerate) { 877 logger.infof("%s Last updated %s", old.index + 1, 878 old.value.updated).collectException; 879 global.data.db.addToWorklist(old.value.id); 880 } 881 }); 882 } 883 884 void opCall(Cleanup data) { 885 global.data.autoCleanup.cleanup; 886 } 887 888 void opCall(ref CheckMutantsLeft data) { 889 spinSql!(() { global.timeoutFsm.execute(*global.data.db); }); 890 891 data.allMutantsTested = global.timeoutFsm.output.done; 892 893 if (global.timeoutFsm.output.done) { 894 logger.info("All mutants are tested").collectException; 895 } 896 } 897 898 void opCall(SaveMutationScore data) { 899 import dextool.plugin.mutate.backend.database.type : MutationScore; 900 import dextool.plugin.mutate.backend.report.analyzers : reportScore; 901 902 if (spinSql!(() => global.data.db.unknownSrcMutants(global.data.kinds)).count != 0) 903 return; 904 905 const score = reportScore(*global.data.db, global.data.kinds).score; 906 907 // 10000 mutation scores is only ~80kbyte. Should be enough entries 908 // without taking up unresonable amount of space. 909 spinSql!(() @trusted { 910 auto t = global.data.db.transaction; 911 global.data.db.putMutationScore(MutationScore(Clock.currTime, 912 typeof(MutationScore.score)(score))); 913 global.data.db.trimMutationScore(10000); 914 t.commit; 915 }); 916 } 917 918 void opCall(ref PreCompileSut data) { 919 import std.stdio : write; 920 import colorlog : color, Color; 921 import proc; 922 923 logger.info("Checking the build command").collectException; 924 compile(global.data.conf.mutationCompile, global.data.conf.buildCmdTimeout, true).match!( 925 (Mutation.Status a) { data.compilationError = true; }, (bool success) { 926 data.compilationError = !success; 927 }); 928 } 929 930 void opCall(FindTestCmds data) { 931 auto cmds = appender!(ShellCommand[])(); 932 foreach (root; global.data.conf.testCommandDir) { 933 try { 934 cmds.put(findExecutables(root.AbsolutePath) 935 .map!(a => ShellCommand([a] ~ global.data.conf.testCommandDirFlag))); 936 } catch (Exception e) { 937 logger.warning(e.msg).collectException; 938 } 939 } 940 941 if (!cmds.data.empty) { 942 global.testCmds ~= cmds.data; 943 runner.put(this.global.testCmds); 944 logger.infof("Found test commands in %s:", 945 global.data.conf.testCommandDir).collectException; 946 foreach (c; cmds.data) { 947 logger.info(c).collectException; 948 } 949 } 950 } 951 952 void opCall(ChooseMode data) { 953 } 954 955 void opCall(PullRequest data) { 956 import std.algorithm : sort; 957 import my.set; 958 import dextool.plugin.mutate.backend.database : MutationStatusId; 959 import dextool.plugin.mutate.backend.type : SourceLoc; 960 961 // deterministic testing of mutants and prioritized by their size. 962 global.mutationOrder = MutationOrder.bySize; 963 964 // make sure they are unique. 965 Set!MutationStatusId mutantIds; 966 967 foreach (kv; local.get!PullRequest.constraint.value.byKeyValue) { 968 const file_id = spinSql!(() => global.data.db.getFileId(kv.key)); 969 if (file_id.isNull) { 970 logger.infof("The file %s do not exist in the database. Skipping...", 971 kv.key).collectException; 972 continue; 973 } 974 975 foreach (l; kv.value) { 976 auto mutants = spinSql!(() { 977 return global.data.db.getMutationsOnLine(global.data.kinds, 978 file_id.get, SourceLoc(l.value, 0)); 979 }); 980 981 const preCnt = mutantIds.length; 982 foreach (v; mutants) { 983 mutantIds.add(v); 984 } 985 986 logger.infof(mutantIds.length - preCnt > 0, "Found %s mutant(s) to test (%s:%s)", 987 mutantIds.length - preCnt, kv.key, l.value).collectException; 988 } 989 } 990 991 logger.infof(!mutantIds.empty, "Found %s mutants in the diff", 992 mutantIds.length).collectException; 993 spinSql!(() { 994 foreach (id; mutantIds.toArray.sort) { 995 // using 100000 to make a pull request mutant very high prio 996 global.data.db.addToWorklist(id, 100000, MutationOrder.bySize); 997 } 998 }); 999 1000 local.get!CheckPullRequestMutant.startWorklistCnt = spinSql!(() { 1001 return global.data.db.getWorklistCount; 1002 }); 1003 local.get!CheckPullRequestMutant.stopAfter = mutantIds.length; 1004 1005 if (mutantIds.empty) { 1006 logger.warning("None of the locations specified with -L exists").collectException; 1007 logger.info("Available files are:").collectException; 1008 foreach (f; spinSql!(() => global.data.db.getFiles)) 1009 logger.info(f).collectException; 1010 } 1011 } 1012 1013 void opCall(ref MeasureTestSuite data) { 1014 import std.algorithm : max, sum; 1015 import dextool.plugin.mutate.backend.database.type : TestCmdRuntime; 1016 1017 if (!global.data.conf.mutationTesterRuntime.isNull) { 1018 global.testSuiteRuntime = global.data.conf.mutationTesterRuntime.get; 1019 return; 1020 } 1021 1022 logger.infof("Measuring the runtime of the test command(s):\n%(%s\n%)", 1023 global.testCmds).collectException; 1024 1025 auto measures = spinSql!(() => global.data.db.getTestCmdRuntimes); 1026 1027 const tester = () { 1028 try { 1029 return measureTestCommand(runner, max(1, cast(int)(3 - measures.length))); 1030 } catch (Exception e) { 1031 logger.error(e.msg).collectException; 1032 return MeasureTestDurationResult(false); 1033 } 1034 }(); 1035 1036 if (tester.ok) { 1037 measures ~= tester.runtime.map!(a => TestCmdRuntime(Clock.currTime, a)).array; 1038 if (measures.length > 3) { 1039 measures = measures[1 .. $]; // drop the oldest 1040 } 1041 1042 auto mean = sum(measures.map!(a => a.runtime), Duration.zero) / measures.length; 1043 1044 // The sampling of the test suite become too unreliable when the timeout is <1s. 1045 // This is a quick and dirty fix. 1046 // A proper fix requires an update of the sampler in runTester. 1047 auto t = mean < 1.dur!"seconds" ? 1.dur!"seconds" : mean; 1048 logger.info("Test command runtime: ", t).collectException; 1049 global.testSuiteRuntime = t; 1050 1051 spinSql!(() @trusted { 1052 auto t = global.data.db.transaction; 1053 global.data.db.setTestCmdRuntimes(measures); 1054 t.commit; 1055 }); 1056 } else { 1057 data.unreliableTestSuite = true; 1058 logger.error("The test command is unreliable. It must return exit status '0' when no mutants are injected") 1059 .collectException; 1060 } 1061 } 1062 1063 void opCall(ref MutationTest data) { 1064 auto p = () @trusted { return &runner; }(); 1065 1066 try { 1067 auto g = MutationTestDriver.Global(global.data.filesysIO, 1068 global.data.db, global.nextMutant, p); 1069 auto driver = MutationTestDriver(g, 1070 MutationTestDriver.TestMutantData(!(global.data.conf.mutationTestCaseAnalyze.empty 1071 && global.data.conf.mutationTestCaseBuiltin.empty), 1072 global.data.conf.mutationCompile, global.data.conf.buildCmdTimeout), 1073 MutationTestDriver.TestCaseAnalyzeData(&testCaseAnalyzer)); 1074 1075 while (driver.isRunning) { 1076 driver.execute(); 1077 } 1078 1079 if (driver.stopBecauseError) { 1080 data.mutationError.get = true; 1081 } else { 1082 data.result = driver.result; 1083 } 1084 } catch (Exception e) { 1085 data.mutationError.get = true; 1086 logger.error(e.msg).collectException; 1087 } 1088 } 1089 1090 void opCall(ref CheckTimeout data) { 1091 data.timeoutUnchanged = global.hardcodedTimeout || global.timeoutFsm.output.done; 1092 } 1093 1094 void opCall(UpdateTimeout) { 1095 spinSql!(() { global.timeoutFsm.execute(*global.data.db); }); 1096 1097 const lastIter = local.get!UpdateTimeout.lastTimeoutIter; 1098 1099 if (lastIter != global.timeoutFsm.output.iter) { 1100 logger.infof("Changed the timeout from %s to %s (iteration %s)", 1101 calculateTimeout(lastIter, global.testSuiteRuntime), 1102 calculateTimeout(global.timeoutFsm.output.iter, global.testSuiteRuntime), 1103 global.timeoutFsm.output.iter).collectException; 1104 local.get!UpdateTimeout.lastTimeoutIter = global.timeoutFsm.output.iter; 1105 } 1106 1107 runner.timeout = calculateTimeout(global.timeoutFsm.output.iter, global.testSuiteRuntime); 1108 } 1109 1110 void opCall(ref CheckPullRequestMutant data) { 1111 const left = spinSql!(() { return global.data.db.getWorklistCount; }); 1112 data.noUnknownMutantsLeft.get = ( 1113 local.get!CheckPullRequestMutant.startWorklistCnt - left) >= local 1114 .get!CheckPullRequestMutant.stopAfter; 1115 1116 logger.infof(stopCheck.aliveMutants > 0, "Found %s/%s alive mutants", 1117 stopCheck.aliveMutants, global.data.conf.maxAlive.get).collectException; 1118 } 1119 1120 void opCall(ref NextMutant data) { 1121 global.nextMutant = MutationEntry.init; 1122 1123 auto next = spinSql!(() { 1124 return global.data.db.nextMutation(global.data.kinds, global.mutationOrder); 1125 }); 1126 1127 data.noUnknownMutantsLeft.get = next.st == NextMutationEntry.Status.done; 1128 1129 if (!next.entry.isNull) 1130 global.nextMutant = next.entry.get; 1131 } 1132 1133 void opCall(HandleTestResult data) { 1134 saveTestResult(data.result); 1135 } 1136 1137 void opCall(ref CheckStopCond data) { 1138 const halt = stopCheck.isHalt; 1139 data.halt = halt != TestStopCheck.HaltReason.none; 1140 1141 final switch (halt) with (TestStopCheck.HaltReason) { 1142 case none: 1143 break; 1144 case maxRuntime: 1145 logger.info(stopCheck.maxRuntimeToString).collectException; 1146 break; 1147 case aliveTested: 1148 logger.info("Alive mutants threshold reached").collectException; 1149 break; 1150 case overloaded: 1151 logger.info(stopCheck.overloadToString).collectException; 1152 break; 1153 } 1154 logger.warning(data.halt, "Halting").collectException; 1155 } 1156 1157 void opCall(ref NextSchemata data) { 1158 auto schematas = local.get!NextSchemata.schematas; 1159 1160 const threshold = schemataMutantsThreshold(global.data.conf.minMutantsPerSchema.get, 1161 local.get!NextSchemata.invalidSchematas, local.get!NextSchemata.totalSchematas); 1162 1163 while (!schematas.empty && !data.hasSchema) { 1164 // TODO: replace with my.collection.vector 1165 const id = schematas[0]; 1166 schematas = schematas[1 .. $]; 1167 const mutants = spinSql!(() { 1168 return global.data.db.schemataMutantsCount(id, global.data.kinds); 1169 }); 1170 1171 logger.infof("Schema %s has %s mutants (threshold %s)", id.get, 1172 mutants, threshold).collectException; 1173 1174 if (mutants >= threshold) { 1175 data.hasSchema.get = true; 1176 data.schemataId = id; 1177 logger.infof("Use schema %s (%s/%s)", id.get, local.get!NextSchemata.totalSchematas - schematas.length, 1178 local.get!NextSchemata.totalSchematas).collectException; 1179 } 1180 } 1181 1182 local.get!NextSchemata.schematas = schematas; 1183 1184 data.stop.get = !data.hasSchema && global.data.conf.stopAfterLastSchema; 1185 } 1186 1187 void opCall(ref SchemataTest data) { 1188 import dextool.plugin.mutate.backend.test_mutant.schemata; 1189 1190 // only remove schemas that are of no further use. 1191 bool remove = true; 1192 void updateRemove(MutationTestResult[] result) { 1193 foreach (a; data.result) { 1194 final switch (a.status) with (Mutation.Status) { 1195 case unknown: 1196 goto case; 1197 case noCoverage: 1198 goto case; 1199 case alive: 1200 remove = false; 1201 return; 1202 case killed: 1203 goto case; 1204 case timeout: 1205 goto case; 1206 case killedByCompiler: 1207 break; 1208 } 1209 } 1210 } 1211 1212 void save(MutationTestResult[] result) { 1213 updateRemove(result); 1214 saveTestResult(result); 1215 logger.infof(result.length > 0, "Saving %s schemata mutant results", 1216 result.length).collectException; 1217 } 1218 1219 try { 1220 auto driver = SchemataTestDriver(global.data.filesysIO, &runner, global.data.db, 1221 &testCaseAnalyzer, global.data.conf.userRuntimeCtrl, data.id, stopCheck, 1222 global.data.kinds, global.data.conf.mutationCompile, 1223 global.data.conf.buildCmdTimeout, global.data.conf.logSchemata); 1224 1225 const saveResultInterval = 20.dur!"minutes"; 1226 auto nextSave = Clock.currTime + saveResultInterval; 1227 while (driver.isRunning) { 1228 driver.execute; 1229 1230 // to avoid loosing results in case of a crash etc save them 1231 // continuously 1232 if (Clock.currTime > nextSave && !driver.hasFatalError) { 1233 save(driver.popResult); 1234 nextSave = Clock.currTime + saveResultInterval; 1235 } 1236 } 1237 1238 data.fatalError = driver.hasFatalError; 1239 1240 if (driver.hasFatalError) { 1241 // do nothing 1242 } else if (driver.isInvalidSchema) { 1243 local.get!NextSchemata.invalidSchematas++; 1244 } else { 1245 save(driver.popResult); 1246 } 1247 1248 if (remove) { 1249 spinSql!(() { global.data.db.markUsed(data.id); }); 1250 } 1251 1252 } catch (Exception e) { 1253 logger.info(e.msg).collectException; 1254 logger.warning("Failed executing schemata ", data.id).collectException; 1255 } 1256 } 1257 1258 void opCall(SchemataPruneUsed data) { 1259 try { 1260 const removed = global.data.db.pruneUsedSchemas; 1261 1262 if (removed != 0) { 1263 logger.infof("Removed %s schemas from the database", removed); 1264 // vacuum the database because schemas take up a significant 1265 // amount of space. 1266 global.data.db.vacuum; 1267 } 1268 } catch (Exception e) { 1269 logger.warning(e.msg).collectException; 1270 } 1271 } 1272 1273 void opCall(LoadSchematas data) { 1274 if (!global.data.conf.useSchemata) { 1275 return; 1276 } 1277 1278 auto app = appender!(SchemataId[])(); 1279 foreach (id; spinSql!(() { return global.data.db.getSchematas(); })) { 1280 if (spinSql!(() { 1281 return global.data.db.schemataMutantsCount(id, global.data.kinds); 1282 }) >= schemataMutantsThreshold(global.data.conf.minMutantsPerSchema.get, 0, 0)) { 1283 app.put(id); 1284 } 1285 } 1286 1287 logger.trace("Found schematas: ", app.data).collectException; 1288 // random reorder to reduce the chance that multipe instances of 1289 // dextool use the same schema 1290 local.get!NextSchemata.schematas = app.data.randomCover.array; 1291 local.get!NextSchemata.totalSchematas = app.data.length; 1292 } 1293 1294 void opCall(ref Coverage data) { 1295 import dextool.plugin.mutate.backend.test_mutant.coverage; 1296 1297 auto tracked = spinSql!(() => global.data.db.getLatestTimeStampOfTestOrSut).orElse( 1298 SysTime.init); 1299 auto covTimeStamp = spinSql!(() => global.data.db.getCoverageTimeStamp).orElse( 1300 SysTime.init); 1301 1302 if (tracked < covTimeStamp) { 1303 logger.info("Coverage information is up to date").collectException; 1304 return; 1305 } else { 1306 logger.infof("Coverage is out of date with SUT/tests (%s < %s)", 1307 covTimeStamp, tracked).collectException; 1308 } 1309 1310 try { 1311 auto driver = CoverageDriver(global.data.filesysIO, global.data.db, &runner, 1312 global.data.conf.userRuntimeCtrl, global.data.conf.mutationCompile, 1313 global.data.conf.buildCmdTimeout, global.data.conf.logCoverage.get); 1314 while (driver.isRunning) { 1315 driver.execute; 1316 } 1317 data.propagate = true; 1318 data.fatalError = driver.hasFatalError; 1319 } catch (Exception e) { 1320 logger.warning(e.msg).collectException; 1321 data.fatalError = true; 1322 } 1323 1324 if (data.fatalError) 1325 logger.warning("Error detected when trying to gather coverage information") 1326 .collectException; 1327 } 1328 1329 void opCall(PropagateCoverage data) { 1330 void propagate() @trusted { 1331 auto trans = global.data.db.transaction; 1332 1333 auto noCov = global.data.db.getNotCoveredMutants; 1334 foreach (id; noCov) { 1335 global.data.db.updateMutationStatus(id, Mutation.Status.noCoverage, ExitStatus(0)); 1336 global.data.db.removeFromWorklist(id); 1337 } 1338 1339 trans.commit; 1340 logger.infof("Marked %s mutants as alive because they where not covered by any test", 1341 noCov.length); 1342 } 1343 1344 spinSql!(() => propagate); 1345 } 1346 1347 void saveTestResult(MutationTestResult[] results) @safe nothrow { 1348 void statusUpdate(MutationTestResult result) @safe { 1349 import dextool.plugin.mutate.backend.test_mutant.timeout : updateMutantStatus; 1350 1351 const cnt_action = () { 1352 if (result.status == Mutation.Status.alive) 1353 return Database.CntAction.incr; 1354 return Database.CntAction.reset; 1355 }(); 1356 1357 global.estimate.update(result.status); 1358 1359 updateMutantStatus(*global.data.db, result.id, result.status, 1360 result.exitStatus, global.timeoutFsm.output.iter); 1361 global.data.db.updateMutation(result.id, result.profile); 1362 global.data.db.updateMutationTestCases(result.id, result.testCases); 1363 global.data.db.removeFromWorklist(result.id); 1364 1365 if (result.status == Mutation.Status.alive) 1366 stopCheck.incrAliveMutants; 1367 } 1368 1369 spinSql!(() @trusted { 1370 auto t = global.data.db.transaction; 1371 foreach (a; results) { 1372 statusUpdate(a); 1373 } 1374 t.commit; 1375 }); 1376 1377 const left = spinSql!(() { return global.data.db.getWorklistCount; }); 1378 logger.infof("%s mutants left to test. Estimated mutation score %.3s (error %.3s)", left, 1379 global.estimate.value.get, global.estimate.error.get).collectException; 1380 } 1381 } 1382 1383 private: 1384 1385 /** A schemata must have at least this many mutants that have the status unknown 1386 * for it to be cost efficient to use schemata. 1387 * 1388 * The weights dynamically adjust with how many of the schemas that has failed 1389 * to compile. 1390 * 1391 * Params: 1392 * checkSchemata = if the user has activated check_schemata that run all test cases before the schemata is used. 1393 */ 1394 long schemataMutantsThreshold(const long minThreshold, const long invalidSchematas, 1395 const long totalSchematas) @safe pure nothrow @nogc { 1396 // "10" is a magic number that felt good but not too conservative. A future 1397 // improvement is to instead base it on the ratio between compilation time 1398 // and test suite execution time. 1399 if (totalSchematas > 0) 1400 return cast(long)(minThreshold + 10.0 * ( 1401 cast(double) invalidSchematas / cast(double) totalSchematas)); 1402 return cast(long) minThreshold; 1403 } 1404 1405 /** Compare the old test cases with those that have been found this run. 1406 * 1407 * TODO: the side effect that this function print to the console is NOT good. 1408 */ 1409 bool hasNewTestCases(ref Set!string old_tcs, ref Set!string found_tcs) @safe nothrow { 1410 bool rval; 1411 1412 auto new_tcs = found_tcs.setDifference(old_tcs); 1413 foreach (tc; new_tcs.toRange) { 1414 logger.info(!rval, "Found new test case(s):").collectException; 1415 logger.infof("%s", tc).collectException; 1416 rval = true; 1417 } 1418 1419 return rval; 1420 } 1421 1422 /** Compare old and new test cases to print those that have been removed. 1423 */ 1424 void printDroppedTestCases(ref Set!string old_tcs, ref Set!string changed_tcs) @safe nothrow { 1425 auto diff = old_tcs.setDifference(changed_tcs); 1426 auto removed = diff.toArray; 1427 1428 logger.info(removed.length != 0, "Detected test cases that has been removed:").collectException; 1429 foreach (tc; removed) { 1430 logger.infof("%s", tc).collectException; 1431 } 1432 } 1433 1434 /// Returns: true if all tests cases have unique identifiers 1435 void warnIfConflictingTestCaseIdentifiers(TestCase[] found_tcs) @safe nothrow { 1436 Set!TestCase checked; 1437 bool conflict; 1438 1439 foreach (tc; found_tcs) { 1440 if (checked.contains(tc)) { 1441 logger.info(!conflict, 1442 "Found test cases that do not have global, unique identifiers") 1443 .collectException; 1444 logger.info(!conflict, 1445 "This make the report of test cases that has killed zero mutants unreliable") 1446 .collectException; 1447 logger.info("%s", tc).collectException; 1448 conflict = true; 1449 } 1450 } 1451 } 1452 1453 private: 1454 1455 /** 1456 DESCRIPTION 1457 1458 The getloadavg() function returns the number of processes in the system 1459 run queue averaged over various periods of time. Up to nelem samples are 1460 retrieved and assigned to successive elements of loadavg[]. The system 1461 imposes a maximum of 3 samples, representing averages over the last 1, 5, 1462 and 15 minutes, respectively. 1463 1464 1465 DIAGNOSTICS 1466 1467 If the load average was unobtainable, -1 is returned; otherwise, the num- 1468 ber of samples actually retrieved is returned. 1469 */ 1470 extern (C) int getloadavg(double* loadavg, int nelem) nothrow;