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