1 /**
2    This module is an attempt to alleviate compile times by including the bare
3    minimum. The idea is that while the reporting usually done by unit-threaded
4    is welcome, it only really matters when tests fail. Otherwise, no news is
5    good news.
6 
7    Likewise, naming and selecting tests are features used when certain tests
8    fail. The usual way to run tests is to run all of them and be happy if
9    they all pass.
10 
11    This module makes it so that unit-threaded gets out of the way, and if
12    needed the full features can be turned on at the cost of compiling
13    much more slowly.
14 
15    There aren't even any template constraints on the `should` functions
16    to avoid imports as much as possible.
17  */
18 module unit_threaded.light;
19 
20 alias UnitTestException = Exception;
21 
22 
23 /**
24    Dummy version so "normal" code compiles
25  */
26 mixin template runTestsMain(Modules...) if(Modules.length > 0) {
27     int main() {
28         import unit_threaded.light: runTestsImpl;
29         return runTestsImpl;
30     }
31 }
32 
33 /**
34    Dummy version of runTests so "normal" code compiles.
35  */
36 int runTests(T...)(in string[] args) {
37     return runTestsImpl;
38 }
39 
40 /// ditto
41 int runTests(T)(string[] args, T testData) {
42     return runTestsImpl;
43 }
44 
45 int runTestsImpl() {
46     import core.runtime: Runtime;
47     import core.stdc.stdio: printf;
48 
49     version(Posix)
50         printf("\033[32;1mOk\033[0;;m");
51     else
52         printf("Ok");
53 
54     printf(": All tests passed\n\n");
55 
56     return 0;
57 }
58 
59 
60 /**
61    Dummy version so "normal" code compiles
62  */
63 int[] allTestData(T...)() {
64     return [];
65 }
66 
67 /**
68    No-op version of writelnUt
69  */
70 void writelnUt(T...)(auto ref T args) {
71 
72 }
73 
74 /**
75    Same as unit_threaded.property.check
76  */
77 void check(alias F)(int numFuncCalls = 100,
78                     string file = __FILE__, size_t line = __LINE__) {
79     import unit_threaded.property: utCheck = check;
80     utCheck!F(numFuncCalls, file, line);
81 }
82 
83 /**
84    Same as unit_threaded.property.checkCustom
85  */
86 void checkCustom(alias Generator, alias Predicate)
87                 (int numFuncCalls = 100, string file = __FILE__, size_t line = __LINE__) {
88     import unit_threaded.property: utCheckCustom = checkCustom;
89     utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line);
90 }
91 
92 
93 /**
94    Generic output interface
95  */
96 interface Output {
97     void send(in string output) @safe;
98     void flush() @safe;
99 }
100 
101 /**
102    Dummy version of unit_threaded.testcase.TestCase
103  */
104 class TestCase {
105     abstract void test();
106     void setup() {}
107     void shutdown() {}
108     static TestCase currentTest() { return new class TestCase { override void test() {}}; }
109     Output getWriter() { return new class Output { override void send(in string output) {} override void flush() {}}; }
110 }
111 
112 
113 /**
114    Same as unit_threaded.mock.mock
115  */
116 auto mock(T)() {
117     import unit_threaded.mock: utMock = mock;
118     return utMock!T;
119 }
120 
121 /**
122    Same as unit_threaded.mock.mockStruct
123  */
124 auto mockStruct(T...)(auto ref T returns) {
125     import unit_threaded.mock: utMockStruct = mockStruct;
126     return utMockStruct(returns);
127 }
128 
129 /**
130    Throw if condition is not true.
131  */
132 void shouldBeTrue(E)(lazy E condition, string file = __FILE__, size_t line = __LINE__) {
133     assert_(cast(bool)condition(), file, line);
134 }
135 
136 /// Throw if condition not false.
137 void shouldBeFalse(E)(lazy E condition, string file = __FILE__, size_t line = __LINE__) {
138     assert_(!cast(bool)condition(), file, line);
139 }
140 
141 /// Assert value is equal to expected
142 void shouldEqual(V, E)(auto ref V value, auto ref E expected, string file = __FILE__, size_t line = __LINE__) {
143 
144     void checkInputRange(T)(auto ref const(T) _) @trusted {
145         auto obj = cast(T) _;
146         bool e = obj.empty;
147         auto f = obj.front;
148         obj.popFront;
149     }
150     enum isInputRange(T) = is(T: Elt[], Elt) || is(typeof(checkInputRange(T.init)));
151 
152     static if(is(V == class)) {
153 
154         import unit_threaded.should: isEqual;
155         assert_(isEqual(value, expected), file, line);
156 
157     } else static if(isInputRange!V && isInputRange!E) {
158 
159         auto ref unqual(OriginalType)(auto ref OriginalType obj) @trusted {
160 
161             // copied from std.traits
162             template Unqual(T) {
163                      static if (is(T U ==          immutable U)) alias Unqual = U;
164                 else static if (is(T U == shared inout const U)) alias Unqual = U;
165                 else static if (is(T U == shared inout       U)) alias Unqual = U;
166                 else static if (is(T U == shared       const U)) alias Unqual = U;
167                 else static if (is(T U == shared             U)) alias Unqual = U;
168                 else static if (is(T U ==        inout const U)) alias Unqual = U;
169                 else static if (is(T U ==        inout       U)) alias Unqual = U;
170                 else static if (is(T U ==              const U)) alias Unqual = U;
171                 else                                             alias Unqual = T;
172             }
173 
174             static if(__traits(compiles, obj[])) {
175                 static if(!is(typeof(obj[]) == OriginalType)) {
176                     return unqual(obj[]);
177                 } else static if(__traits(compiles, cast(Unqual!OriginalType) obj)) {
178                     return cast(Unqual!OriginalType) obj;
179                 } else {
180                     return obj;
181                 }
182             } else  static if(__traits(compiles, cast(Unqual!OriginalType) obj)) {
183                 return cast(Unqual!OriginalType) obj;
184             } else
185                 return obj;
186         }
187 
188         auto ref unvoid(OriginalType)(auto ref OriginalType obj) @trusted {
189             static if(is(OriginalType == void[]))
190                 return cast(ubyte[]) obj;
191             else
192                 return obj;
193         }
194 
195         import std.algorithm: equal;
196         assert_(equal(unvoid(unqual(value)), unvoid(unqual(expected))), file, line);
197 
198     } else {
199         assert_(value == expected, file, line);
200     }
201 }
202 
203 
204 
205 /// Assert value is not equal to expected.
206 void shouldNotEqual(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) {
207     assert_(value != expected, file, line);
208 }
209 
210 /// Assert value is null.
211 void shouldBeNull(T)(const scope auto ref T value, string file = __FILE__, size_t line = __LINE__) {
212     assert_(value is null, file, line);
213 }
214 
215 /// Assert value is not null
216 void shouldNotBeNull(T)(const scope auto ref T value, string file = __FILE__, size_t line = __LINE__) {
217     assert_(value !is null, file, line);
218 }
219 
220 enum isLikeAssociativeArray(T, K) = is(typeof({
221     if(K.init in T) { }
222     if(K.init !in T) { }
223 }));
224 static assert(isLikeAssociativeArray!(string[string], string));
225 static assert(!isLikeAssociativeArray!(string[string], int));
226 
227 
228 /// Assert that value is in container.
229 void shouldBeIn(T, U)(const scope auto ref T value, const scope auto ref U container, string file = __FILE__, size_t line = __LINE__)
230     if(isLikeAssociativeArray!(U, T)) {
231     assert_(cast(bool)(value in container), file, line);
232 }
233 
234 /// ditto.
235 void shouldBeIn(T, U)(const scope auto ref T value, U container, string file = __FILE__, size_t line = __LINE__)
236     if (!isLikeAssociativeArray!(U, T))
237 {
238     import std.algorithm: find;
239     import std.array: empty;
240     assert_(!find(container, value).empty, file, line);
241 }
242 
243 /// Assert value is not in container.
244 void shouldNotBeIn(T, U)(const scope auto ref T value, const scope auto ref U container, string file = __FILE__, size_t line = __LINE__)
245     if(isLikeAssociativeArray!U) {
246     assert_(!cast(bool)(value in container), file, line);
247 }
248 
249 /// ditto.
250 void shouldNotBeIn(T, U)(const scope auto ref T value, U container, string file = __FILE__, size_t line = __LINE__)
251     if (!isLikeAssociativeArray!(U, T))
252 {
253     import std.algorithm: find;
254     import std.array: empty;
255     assert_(find(container, value).empty, file, line);
256 }
257 
258 /// Assert that expr throws.
259 void shouldThrow(T : Throwable = Exception, E)
260                 (lazy E expr, string file = __FILE__, size_t line = __LINE__) {
261     import std.traits: isSafe, isUnsafe;
262 
263     static if(isUnsafe!expr)
264         void callExpr() @system { expr(); }
265     else
266         void callExpr() @safe   { expr(); }
267 
268     bool impl() {
269         try {
270             callExpr;
271             return false;
272         } catch(T _) {
273             return true;
274         }
275     }
276 
277     static if(isSafe!callExpr)
278         const threw = () @trusted { return impl; }();
279     else
280         const threw = impl;
281 
282     assert_(threw, file, line);
283 }
284 
285 /// Assert that expr throws an Exception that must have the type E, derived types won't do.
286 void shouldThrowExactly(T : Throwable = Exception, E)
287                        (lazy E expr, string file = __FILE__, size_t line = __LINE__)
288 {
289     import std.traits: isSafe, isUnsafe;
290 
291     TypeInfo typeInfo = null;
292 
293     static if(isUnsafe!expr)
294         void callExpr() @system { expr(); }
295     else
296         void callExpr() @safe   { expr(); }
297 
298     void impl() {
299         try
300             callExpr;
301         catch(T t) {
302             typeInfo = typeid(t);
303         }
304     }
305 
306     static if(isSafe!callExpr)
307         () @trusted { return impl; }();
308     else
309         impl;
310 
311     // Object.opEquals is @system and impure
312     const sameType = () @trusted { return typeInfo !is null && typeInfo == typeid(T); }();
313     assert_(sameType, file, line);
314 }
315 
316 
317 /// Assert that expr doesn't throw
318 void shouldNotThrow(T: Throwable = Exception, E)
319                    (lazy E expr, string file = __FILE__, size_t line = __LINE__) {
320 
321     import std.traits: isSafe, isUnsafe;
322 
323     static if(isUnsafe!expr)
324         void callExpr() @system { expr(); }
325     else
326         void callExpr() @safe   { expr(); }
327 
328     void impl() {
329         try
330             callExpr;
331         catch(T t) {
332             assert_(false, file, line);
333         }
334     }
335 
336     static if(isSafe!callExpr)
337         () @trusted { return impl; }();
338     else
339         impl;
340 }
341 
342 /// Assert that expr throws and the exception message is msg.
343 void shouldThrowWithMessage(T : Throwable = Exception, E)
344                            (lazy E expr,
345                             string msg,
346                             string file = __FILE__,
347                             size_t line = __LINE__)
348     in(msg != "")
349     do
350 {
351     import std.traits: isSafe, isUnsafe;
352 
353     string thrownMsg;
354 
355     static if(isUnsafe!expr)
356         void callExpr() @system { expr(); }
357     else
358         void callExpr() @safe   { expr(); }
359 
360     void impl() {
361         try
362             callExpr;
363         catch(T t) {
364             thrownMsg = t.msg;
365         }
366     }
367 
368     static if(isSafe!callExpr)
369         () @trusted { return impl; }();
370     else
371         impl;
372 
373     assert_(thrownMsg == msg, file, line);
374 }
375 
376 /// Assert that value is approximately equal to expected.
377 void shouldApproxEqual(V, E)
378                       (in V value, in E expected, double maxRelDiff = 1e-2, double maxAbsDiff = 1e-5, string file = __FILE__, size_t line = __LINE__)
379 {
380     import std.math: approxEqual;
381     assert_(approxEqual(value, expected, maxRelDiff, maxAbsDiff), file, line);
382 }
383 
384 /// assert that rng is empty.
385 void shouldBeEmpty(R)(const scope auto ref R rng, string file = __FILE__, size_t line = __LINE__) {
386     import std.range: isInputRange;
387     import std.traits: isAssociativeArray;
388     import std.array;
389 
390     static if(isInputRange!R)
391         assert_(rng.empty, file, line);
392     else static if(isAssociativeArray!R)
393         () @trusted { assert_(rng.keys.empty, file, line); }();
394     else
395         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
396 }
397 
398 /// Assert that rng is not empty.
399 void shouldNotBeEmpty(R)(const scope auto ref R rng, string file = __FILE__, size_t line = __LINE__) {
400     import std.range: isInputRange;
401     import std.traits: isAssociativeArray;
402     import std.array;
403 
404     static if(isInputRange!R)
405         assert_(!rnd.empty, file, line);
406     else static if(isAssociativeArray!R)
407         () @trusted { assert_(!rng.keys.empty, file, line); }();
408     else
409         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
410 }
411 
412 /// Assert that t should be greater than u.
413 void shouldBeGreaterThan(T, U)(const scope auto ref T t, const scope auto ref U u,
414                                string file = __FILE__, size_t line = __LINE__)
415 {
416     assert_(t > u, file, line);
417 }
418 
419 /// Assert that t should be smaller than u.
420 void shouldBeSmallerThan(T, U)(const scope auto ref T t, const scope auto ref U u,
421                                string file = __FILE__, size_t line = __LINE__)
422 {
423     assert_(t < u, file, line);
424 }
425 
426 /// Assert that value is the same set as expected (i.e. order doesn't matter)
427 void shouldBeSameSetAs(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) {
428     assert_(isSameSet(value, expected), file, line);
429 }
430 
431 /// Assert that value is not the same set as expected.
432 void shouldNotBeSameSetAs(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) {
433     assert_(!isSameSet(value, expected), file, line);
434 }
435 
436 private bool isSameSet(T, U)(const scope auto ref T t, const scope auto ref U u) {
437     import std.array: array;
438     import std.algorithm: canFind;
439 
440     //sort makes the element types have to implement opCmp
441     //instead, try one by one
442     auto ta = t.array;
443     auto ua = u.array;
444     if (ta.length != ua.length) return false;
445     foreach(element; ta)
446     {
447         if (!ua.canFind(element)) return false;
448     }
449 
450     return true;
451 }
452 
453 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter)
454 void shouldBeSameJsonAs(in string actual,
455                         in string expected,
456                         string file = __FILE__,
457                         size_t line = __LINE__)
458     @trusted // not @safe pure due to parseJSON
459 {
460     import std.json: parseJSON, JSONException;
461 
462     auto parse(in string str) {
463         try
464             return str.parseJSON;
465         catch(JSONException ex) {
466             assert_(false, "Failed to parse " ~ str, file, line);
467         }
468         assert(0);
469     }
470 
471     assert_(parse(actual) == parse(expected), file, line);
472 }
473 
474 
475 private void assert_(in bool value, string file, size_t line) @safe pure {
476     assert_(value, "Assertion failure", file, line);
477 }
478 
479 private void assert_(bool value, string message, string file, size_t line) @safe pure {
480     if(!value)
481         throw new Exception(message, file, line);
482 }
483 
484 void fail(in string output, string file, size_t line) @safe pure {
485     assert_(false, output, file, line);
486 }
487 
488 
489 auto should(E)(lazy E expr) {
490 
491     struct Should {
492 
493         bool opEquals(U)(auto ref U other,
494                          string file = __FILE__,
495                          size_t line = __LINE__)
496         {
497             expr.shouldEqual(other, file, line);
498             return true;
499         }
500     }
501 
502     return Should();
503 }