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 }