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