1 /** 2 * This module implements functions to run the unittests with 3 * command-line options. 4 */ 5 6 module unit_threaded.runner.runner; 7 8 import unit_threaded.from; 9 10 /** 11 * Runs all tests in passed-in modules. Modules can be symbols or 12 * strings but they can't mix and match - either all symbols or all 13 * strings. It's recommended to use strings since then the modules don't 14 * have to be imported first. 15 * Generates a main function and substitutes the default D 16 * runtime unittest runner. This mixin should be used instead of 17 * $(D runTests) if a shared library is used instead of an executable. 18 */ 19 mixin template runTestsMain(Modules...) if(Modules.length > 0) { 20 int main(string[] args) { 21 import unit_threaded.runner.runner: runTests; 22 return runTests!Modules(args); 23 } 24 } 25 26 /** 27 * Runs all tests in passed-in modules. Modules can be symbols or 28 * strings but they can't mix and match - either all symbols or all 29 * strings. It's recommended to use strings since then the modules don't 30 * have to be imported first. 31 * Arguments are taken from the command-line. 32 * -s Can be passed to run in single-threaded mode. The rest 33 * of argv is considered to be test names to be run. 34 * Params: 35 * args = Arguments passed to main. 36 * Returns: An integer suitable for the program's return code. 37 */ 38 template runTests(Modules...) if(Modules.length > 0) { 39 40 mixin disableDefaultRunner; 41 42 int runTests(string[] args) nothrow { 43 import unit_threaded.runner.reflection: allTestData; 44 return .runTests(args, allTestData!Modules); 45 } 46 47 int runTests(string[] args, 48 in from!"unit_threaded.runner.reflection".TestData[] testData) 49 nothrow 50 { 51 import unit_threaded.runner.reflection: allTestData; 52 return .runTests(args, allTestData!Modules); 53 } 54 } 55 56 57 /** 58 A template mixin for a static constructor that disables druntimes's 59 default test runner so that unit-threaded can take over. 60 */ 61 mixin template disableDefaultRunner() { 62 shared static this() nothrow { 63 import unit_threaded.runner.runner: replaceModuleUnitTester; 64 replaceModuleUnitTester; 65 } 66 } 67 68 69 /** 70 Generates a main function for collectAndRunTests. 71 */ 72 mixin template collectAndRunTestsMain(Modules...) { 73 int main(string[] args) { 74 import unit_threaded.runner.runner: collectAndRunTests; 75 return collectAndRunTests!Modules(args); 76 } 77 } 78 79 /** 80 Collects test data from each module in Modules and runs tests 81 with the supplied command-line arguments. 82 83 Each module in the list must be a string and the respective D 84 module must define a module-level function called `testData` 85 that returns TestData (obtained by calling allTestData on a list 86 of modules to reflect to). This convoluted way of discovering and 87 running tests is offered to possibly distribute the compile-time 88 price of using reflection to find tests. This is advanced usage. 89 */ 90 template collectAndRunTests(Modules...) { 91 92 mixin disableDefaultRunner; 93 94 int collectAndRunTests(string[] args) { 95 96 import unit_threaded.runner.reflection: TestData; 97 98 const(TestData)[] data; 99 100 static foreach(module_; Modules) { 101 static assert(is(typeof(module_) == string)); 102 mixin(`static import `, module_, `;`); 103 data ~= mixin(module_, `.testData()`); 104 } 105 106 return runTests(args, data); 107 } 108 } 109 110 111 /** 112 * Runs all tests in passed-in testData. Arguments are taken from the 113 * command-line. `-s` Can be passed to run in single-threaded mode. The 114 * rest of argv is considered to be test names to be run. 115 * Params: 116 * args = Arguments passed to main. 117 * testData = Data about the tests to run. 118 * Returns: An integer suitable for the program's return code. 119 */ 120 int runTests(string[] args, 121 in from!"unit_threaded.runner.reflection".TestData[] testData) 122 nothrow 123 { 124 import unit_threaded.runner.options: Options, getOptions; 125 126 Options options; 127 128 try 129 options = getOptions(args); 130 catch(Exception e) { 131 handleException(e); 132 return 1; 133 } 134 135 return runTests(options, testData); 136 } 137 138 int runTests(in from!"unit_threaded.runner.options".Options options, 139 in from!"unit_threaded.runner.reflection".TestData[] testData) 140 nothrow 141 { 142 import unit_threaded.runner.testsuite: TestSuite; 143 144 int impl() { 145 handleCmdLineOptions(options, testData); 146 if (options.exit) 147 return 0; 148 149 auto suite = TestSuite(options, testData); 150 return suite.run ? 0 : 1; 151 } 152 153 try 154 return impl; 155 catch(Exception e) { 156 handleException(e); 157 return 1; 158 } 159 } 160 161 private void handleException(Exception e) @safe nothrow { 162 try { 163 import std.stdio: stderr; 164 () @trusted { stderr.writeln("Error: ", e.msg); }(); 165 } catch(Exception oops) { 166 import core.stdc.stdio: fprintf, stderr; 167 () @trusted { fprintf(stderr, "Error: exception thrown and stderr.writeln failed\n"); }(); 168 } 169 } 170 171 private void handleCmdLineOptions(in from!"unit_threaded.runner.options".Options options, 172 in from!"unit_threaded.runner.reflection".TestData[] testData) 173 { 174 175 import unit_threaded.runner.io: enableDebugOutput, forceEscCodes; 176 import unit_threaded.runner.testcase: enableStackTrace; 177 import std.algorithm: map; 178 179 if (options.list) { 180 import std.stdio: writeln; 181 182 writeln("Listing tests:"); 183 foreach (test; testData.map!(a => a.name)) { 184 writeln(test); 185 } 186 } 187 188 if (options.debugOutput) 189 enableDebugOutput(); 190 191 if (options.forceEscCodes) 192 forceEscCodes(); 193 194 if (options.stackTraces) 195 enableStackTrace(); 196 } 197 198 199 /** 200 * Replace the D runtime's normal unittest block tester. If this is not done, 201 * the tests will run twice. 202 */ 203 void replaceModuleUnitTester() nothrow { 204 import core.runtime: Runtime; 205 try 206 Runtime.moduleUnitTester = &moduleUnitTester; 207 catch(Exception e) { 208 handleException(e); 209 import core.stdc.stdio: fprintf, stderr; 210 fprintf(stderr, "Error: failed to replace Runtime.moduleUnitTester\n"); 211 assert(0, "Inconceivable!"); 212 } 213 } 214 215 216 /** 217 * Replacement for the usual unittest runner. Since unit_threaded 218 * runs the tests itself, the moduleUnitTester doesn't really have to do anything. 219 */ 220 private bool moduleUnitTester() { 221 //this is so unit-threaded's own tests run 222 version(testing_unit_threaded) { 223 import std.algorithm: startsWith; 224 foreach(module_; ModuleInfo) { 225 if(module_ && module_.unitTest && 226 module_.name.startsWith("unit_threaded") && // we want to run the "normal" unit tests 227 //!module_.name.startsWith("unit_threaded.property") && // left here for fast iteration when developing 228 !module_.name.startsWith("unit_threaded.ut.modules")) //but not the ones from the test modules 229 { 230 import std.stdio: writeln; 231 writeln("Running unit-threaded UT for module " ~ module_.name); 232 module_.unitTest()(); 233 234 } 235 } 236 } 237 238 return true; 239 }