/
runner.d
239 lines (197 loc) · 7 KB
/
runner.d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
/**
* This module implements functions to run the unittests with
* command-line options.
*/
module unit_threaded.runner.runner;
import unit_threaded.from;
/**
* Runs all tests in passed-in modules. Modules can be symbols or
* strings but they can't mix and match - either all symbols or all
* strings. It's recommended to use strings since then the modules don't
* have to be imported first.
* Generates a main function and substitutes the default D
* runtime unittest runner. This mixin should be used instead of
* $(D runTests) if a shared library is used instead of an executable.
*/
mixin template runTestsMain(Modules...) if(Modules.length > 0) {
int main(string[] args) {
import unit_threaded.runner.runner: runTests;
return runTests!Modules(args);
}
}
/**
* Runs all tests in passed-in modules. Modules can be symbols or
* strings but they can't mix and match - either all symbols or all
* strings. It's recommended to use strings since then the modules don't
* have to be imported first.
* Arguments are taken from the command-line.
* -s Can be passed to run in single-threaded mode. The rest
* of argv is considered to be test names to be run.
* Params:
* args = Arguments passed to main.
* Returns: An integer suitable for the program's return code.
*/
template runTests(Modules...) if(Modules.length > 0) {
mixin disableDefaultRunner;
int runTests(string[] args) nothrow {
import unit_threaded.runner.reflection: allTestData;
return .runTests(args, allTestData!Modules);
}
int runTests(string[] args,
in from!"unit_threaded.runner.reflection".TestData[] testData)
nothrow
{
import unit_threaded.runner.reflection: allTestData;
return .runTests(args, allTestData!Modules);
}
}
/**
A template mixin for a static constructor that disables druntimes's
default test runner so that unit-threaded can take over.
*/
mixin template disableDefaultRunner() {
shared static this() nothrow {
import unit_threaded.runner.runner: replaceModuleUnitTester;
replaceModuleUnitTester;
}
}
/**
Generates a main function for collectAndRunTests.
*/
mixin template collectAndRunTestsMain(Modules...) {
int main(string[] args) {
import unit_threaded.runner.runner: collectAndRunTests;
return collectAndRunTests!Modules(args);
}
}
/**
Collects test data from each module in Modules and runs tests
with the supplied command-line arguments.
Each module in the list must be a string and the respective D
module must define a module-level function called `testData`
that returns TestData (obtained by calling allTestData on a list
of modules to reflect to). This convoluted way of discovering and
running tests is offered to possibly distribute the compile-time
price of using reflection to find tests. This is advanced usage.
*/
template collectAndRunTests(Modules...) {
mixin disableDefaultRunner;
int collectAndRunTests(string[] args) {
import unit_threaded.runner.reflection: TestData;
const(TestData)[] data;
static foreach(module_; Modules) {
static assert(is(typeof(module_) == string));
mixin(`static import `, module_, `;`);
data ~= mixin(module_, `.testData()`);
}
return runTests(args, data);
}
}
/**
* Runs all tests in passed-in testData. Arguments are taken from the
* command-line. `-s` Can be passed to run in single-threaded mode. The
* rest of argv is considered to be test names to be run.
* Params:
* args = Arguments passed to main.
* testData = Data about the tests to run.
* Returns: An integer suitable for the program's return code.
*/
int runTests(string[] args,
in from!"unit_threaded.runner.reflection".TestData[] testData)
nothrow
{
import unit_threaded.runner.options: Options;
Options options;
try
options = Options(args);
catch(Exception e) {
handleException(e);
return 1;
}
return runTests(options, testData);
}
int runTests(in from!"unit_threaded.runner.options".Options options,
in from!"unit_threaded.runner.reflection".TestData[] testData)
nothrow
{
import unit_threaded.runner.testsuite: TestSuite;
int impl() {
handleCmdLineOptions(options, testData);
if (options.exit)
return 0;
auto suite = TestSuite(options, testData);
return suite.run ? 0 : 1;
}
try
return impl;
catch(Exception e) {
handleException(e);
return 1;
}
}
private void handleException(Exception e) @safe nothrow {
try {
import std.stdio: stderr;
() @trusted { stderr.writeln("Error: ", e.msg); }();
} catch(Exception oops) {
import core.stdc.stdio: fprintf, stderr;
() @trusted { fprintf(stderr, "Error: exception thrown and stderr.writeln failed\n"); }();
}
}
private void handleCmdLineOptions(in from!"unit_threaded.runner.options".Options options,
in from!"unit_threaded.runner.reflection".TestData[] testData)
{
import unit_threaded.runner.io: enableDebugOutput, forceEscCodes;
import unit_threaded.runner.testcase: enableStackTrace;
import std.algorithm: map;
if (options.list) {
import std.stdio: writeln;
writeln("Listing tests:");
foreach (test; testData.map!(a => a.name)) {
writeln(test);
}
}
if (options.debugOutput)
enableDebugOutput();
if (options.forceEscCodes)
forceEscCodes();
if (options.stackTraces)
enableStackTrace();
}
/**
* Replace the D runtime's normal unittest block tester. If this is not done,
* the tests will run twice.
*/
void replaceModuleUnitTester() nothrow {
import core.runtime: Runtime;
try
Runtime.moduleUnitTester = &moduleUnitTester;
catch(Exception e) {
handleException(e);
import core.stdc.stdio: fprintf, stderr;
fprintf(stderr, "Error: failed to replace Runtime.moduleUnitTester\n");
assert(0, "Inconceivable!");
}
}
/**
* Replacement for the usual unittest runner. Since unit_threaded
* runs the tests itself, the moduleUnitTester doesn't really have to do anything.
*/
private bool moduleUnitTester() {
//this is so unit-threaded's own tests run
version(testing_unit_threaded) {
import std.algorithm: startsWith;
foreach(module_; ModuleInfo) {
if(module_ && module_.unitTest &&
module_.name.startsWith("unit_threaded") && // we want to run the "normal" unit tests
//!module_.name.startsWith("unit_threaded.property") && // left here for fast iteration when developing
!module_.name.startsWith("unit_threaded.ut.modules")) //but not the ones from the test modules
{
import std.stdio: writeln;
writeln("Running unit-threaded UT for module " ~ module_.name);
module_.unitTest()();
}
}
}
return true;
}