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 shared static this() { 64 import unit_threaded.runner.runner: replaceModuleUnitTester; 65 replaceModuleUnitTester; 66 } 67 68 int runTests(string[] args) { 69 import unit_threaded.runner.reflection: allTestData; 70 return runTests(args, allTestData!Modules); 71 } 72 } 73 74 75 76 /** 77 * Runs all tests in passed-in testData. Arguments are taken from the 78 * command-line. `-s` Can be passed to run in single-threaded mode. The 79 * rest of argv is considered to be test names to be run. 80 * Params: 81 * args = Arguments passed to main. 82 * testData = Data about the tests to run. 83 * Returns: An integer suitable for the program's return code. 84 */ 85 int runTests(string[] args, in from!"unit_threaded.runner.reflection".TestData[] testData) { 86 import unit_threaded.runner.options: getOptions; 87 return runTests(getOptions(args), testData); 88 } 89 90 int runTests(in from!"unit_threaded.runner.options".Options options, 91 in from!"unit_threaded.runner.reflection".TestData[] testData) 92 { 93 import unit_threaded.runner.testsuite: TestSuite; 94 95 handleCmdLineOptions(options, testData); 96 if (options.exit) 97 return 0; 98 99 auto suite = TestSuite(options, testData); 100 return suite.run ? 0 : 1; 101 } 102 103 104 private void handleCmdLineOptions(in from!"unit_threaded.runner.options".Options options, 105 in from!"unit_threaded.runner.reflection".TestData[] testData) 106 { 107 108 import unit_threaded.runner.io: enableDebugOutput, forceEscCodes; 109 import unit_threaded.runner.testcase: enableStackTrace; 110 import std.algorithm: map; 111 112 if (options.list) { 113 import std.stdio: writeln; 114 115 writeln("Listing tests:"); 116 foreach (test; testData.map!(a => a.name)) { 117 writeln(test); 118 } 119 } 120 121 if (options.debugOutput) 122 enableDebugOutput(); 123 124 if (options.forceEscCodes) 125 forceEscCodes(); 126 127 if (options.stackTraces) 128 enableStackTrace(); 129 } 130 131 132 /** 133 * Replace the D runtime's normal unittest block tester. If this is not done, 134 * the tests will run twice. 135 */ 136 void replaceModuleUnitTester() { 137 import core.runtime: Runtime; 138 Runtime.moduleUnitTester = &moduleUnitTester; 139 } 140 141 142 /** 143 * Replacement for the usual unittest runner. Since unit_threaded 144 * runs the tests itself, the moduleUnitTester doesn't really have to do anything. 145 */ 146 private bool moduleUnitTester() { 147 //this is so unit-threaded's own tests run 148 import std.algorithm: startsWith; 149 foreach(module_; ModuleInfo) { 150 if(module_ && module_.unitTest && 151 module_.name.startsWith("unit_threaded") && // we want to run the "normal" unit tests 152 //!module_.name.startsWith("unit_threaded.property") && // left here for fast iteration when developing 153 !module_.name.startsWith("unit_threaded.ut.modules")) //but not the ones from the test modules 154 { 155 version(testing_unit_threaded) { 156 import std.stdio: writeln; 157 writeln("Running unit-threaded UT for module " ~ module_.name); 158 } 159 module_.unitTest()(); 160 161 } 162 } 163 164 return true; 165 }