1 /** 2 * This module implements $(D TestSuite), an aggregator for $(D TestCase) 3 * objects to run all tests. 4 */ 5 6 module unit_threaded.testsuite; 7 8 import unit_threaded.testcase; 9 import unit_threaded.io; 10 import unit_threaded.options; 11 import unit_threaded.factory; 12 import std.datetime; 13 import std.parallelism : taskPool; 14 import std.algorithm; 15 import std.conv : text; 16 import std.array; 17 import core.runtime; 18 19 /* 20 * taskPool.amap only works with public functions, not closures. 21 */ 22 auto runTest(TestCase test) 23 { 24 return test(); 25 } 26 27 /** 28 * Responsible for running tests and printing output. 29 */ 30 struct TestSuite 31 { 32 /** 33 * Params: 34 * options = The options to run tests with. 35 * testData = The information about the tests to run. 36 */ 37 this(in Options options, in TestData[] testData) { 38 _options = options; 39 _testData = testData; 40 _testCases = createTestCases(testData, options.testsToRun); 41 WriterThread.start; 42 } 43 44 ~this() { 45 WriterThread.get.join; 46 } 47 48 /** 49 * Runs all test cases. 50 * Returns: true if no test failed, false otherwise. 51 */ 52 bool run() { 53 if (!_testCases.length) { 54 utWritelnRed("Error! No tests to run for args: "); 55 utWriteln(_options.testsToRun); 56 return false; 57 } 58 59 immutable elapsed = doRun(); 60 61 if (!numTestsRun) { 62 utWriteln("Did not run any tests!!!"); 63 return false; 64 } 65 66 utWriteln("\nTime taken: ", elapsed); 67 utWrite(numTestsRun, " test(s) run, "); 68 const failuresStr = text(_failures.length, " failed"); 69 if (_failures.length) { 70 utWriteRed(failuresStr); 71 } else { 72 utWrite(failuresStr); 73 } 74 75 void printAbout(string attr)(in string msg) { 76 const num = _testData.filter!(a => mixin("a. " ~ attr)).count; 77 if (num) { 78 utWrite(", "); 79 utWriteYellow(num, " " ~ msg); 80 } 81 } 82 83 printAbout!"hidden"("hidden"); 84 printAbout!"shouldFail"("failing as expected"); 85 86 utWriteln(".\n"); 87 88 if (_failures.length) { 89 utWritelnRed("Unit tests failed!\n"); 90 return false; //oops 91 } 92 93 utWritelnGreen("OK!\n"); 94 95 return true; 96 } 97 98 private: 99 100 const(Options) _options; 101 const(TestData)[] _testData; 102 TestCase[] _testCases; 103 string[] _failures; 104 StopWatch _stopWatch; 105 106 /** 107 * Runs the tests with the given options. 108 * Returns: how long it took to run. 109 */ 110 Duration doRun() { 111 auto tests = getTests(); 112 _stopWatch.start(); 113 114 if (_options.multiThreaded) { 115 _failures = reduce!((a, b) => a ~ b)(_failures, taskPool.amap!runTest(tests)); 116 } else { 117 foreach (test; tests) 118 _failures ~= test(); 119 } 120 121 handleFailures(); 122 123 _stopWatch.stop(); 124 return cast(Duration) _stopWatch.peek(); 125 } 126 127 auto getTests() { 128 auto tests = _testCases.dup; 129 if (_options.random) { 130 import std.random; 131 132 auto generator = Random(_options.seed); 133 tests.randomShuffle(generator); 134 utWriteln("Running tests in random order. ", 135 "To repeat this run, use --seed ", _options.seed); 136 } 137 return tests; 138 } 139 140 void handleFailures() const { 141 if (!_failures.empty) 142 utWriteln(""); 143 foreach (failure; _failures) { 144 utWrite("Test ", failure, " "); 145 utWriteRed("failed"); 146 utWriteln("."); 147 } 148 if (!_failures.empty) 149 utWriteln(""); 150 } 151 152 @property ulong numTestsRun() @trusted const { 153 return _testCases.map!(a => a.numTestsRun).reduce!((a, b) => a + b); 154 } 155 } 156 157 /** 158 * Replace the D runtime's normal unittest block tester. If this is not done, 159 * the tests will run twice. 160 */ 161 void replaceModuleUnitTester() { 162 import core.runtime; 163 164 Runtime.moduleUnitTester = &moduleUnitTester; 165 } 166 167 shared static this() { 168 replaceModuleUnitTester(); 169 } 170 171 /** 172 * Replacement for the usual unittest runner. Since unit_threaded 173 * runs the tests itself, the moduleUnitTester doesn't really have to do anything. 174 */ 175 private bool moduleUnitTester() { 176 //this is so unit-threaded's own tests run 177 foreach(module_; ModuleInfo) { 178 if(module_ && module_.unitTest) { 179 if(startsWith(module_.name, "unit_threaded.")) { 180 module_.unitTest()(); 181 } 182 } 183 } 184 185 return true; 186 }