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 version(unitThreadedLight) {
119 
120     shared static this() {
121         import std.algorithm: canFind;
122         import std.parallelism: parallel;
123         import core.runtime: Runtime;
124 
125         Runtime.moduleUnitTester = () {
126 
127             // ModuleInfo has opApply, can't use parallel on that so we collect
128             // all the modules with unit tests first
129             ModuleInfo*[] modules;
130             foreach(module_; ModuleInfo) {
131                 if(module_ && module_.unitTest)
132                     modules ~= module_;
133             }
134 
135             version(unitUnthreaded)
136                 enum singleThreaded = true;
137             else
138                 const singleThreaded = Runtime.args.canFind("-s") || Runtime.args.canFind("--single");
139 
140             if(singleThreaded)
141                 foreach(module_; modules)
142                     module_.unitTest()();
143              else
144                 foreach(module_; modules.parallel)
145                     module_.unitTest()();
146 
147             return true;
148         };
149     }
150 }
151 
152 /**
153  * Replacement for the usual unittest runner. Since unit_threaded
154  * runs the tests itself, the moduleUnitTester doesn't really have to do anything.
155  */
156 private bool moduleUnitTester() {
157     //this is so unit-threaded's own tests run
158     import std.algorithm: startsWith;
159     foreach(module_; ModuleInfo) {
160         if(module_ && module_.unitTest &&
161            module_.name.startsWith("unit_threaded") && // we want to run the "normal" unit tests
162            //!module_.name.startsWith("unit_threaded.property") && // left here for fast iteration when developing
163            !module_.name.startsWith("unit_threaded.ut.modules")) //but not the ones from the test modules
164         {
165             version(testing_unit_threaded) {
166                 import std.stdio: writeln;
167                 writeln("Running unit-threaded UT for module " ~ module_.name);
168             }
169             module_.unitTest()();
170 
171         }
172     }
173 
174     return true;
175 }