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 }