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 }