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     static if (__VERSION__ >= 2096)
381         import std.math: close = isClose;
382     else
383         import std.math: close = approxEqual;
384     assert_(close(value, expected, maxRelDiff, maxAbsDiff), file, line);
385 }
386 
387 /// assert that rng is empty.
388 void shouldBeEmpty(R)(const scope auto ref R rng, string file = __FILE__, size_t line = __LINE__) {
389     import std.range: isInputRange;
390     import std.traits: isAssociativeArray;
391     import std.array;
392 
393     static if(isInputRange!R)
394         assert_(rng.empty, file, line);
395     else static if(isAssociativeArray!R)
396         () @trusted { assert_(rng.keys.empty, file, line); }();
397     else
398         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
399 }
400 
401 /// Assert that rng is not empty.
402 void shouldNotBeEmpty(R)(const scope auto ref R rng, string file = __FILE__, size_t line = __LINE__) {
403     import std.range: isInputRange;
404     import std.traits: isAssociativeArray;
405     import std.array;
406 
407     static if(isInputRange!R)
408         assert_(!rnd.empty, file, line);
409     else static if(isAssociativeArray!R)
410         () @trusted { assert_(!rng.keys.empty, file, line); }();
411     else
412         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
413 }
414 
415 /// Assert that t should be greater than u.
416 void shouldBeGreaterThan(T, U)(const scope auto ref T t, const scope auto ref U u,
417                                string file = __FILE__, size_t line = __LINE__)
418 {
419     assert_(t > u, file, line);
420 }
421 
422 /// Assert that t should be smaller than u.
423 void shouldBeSmallerThan(T, U)(const scope auto ref T t, const scope auto ref U u,
424                                string file = __FILE__, size_t line = __LINE__)
425 {
426     assert_(t < u, file, line);
427 }
428 
429 /// Assert that value is the same set as expected (i.e. order doesn't matter)
430 void shouldBeSameSetAs(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) {
431     assert_(isSameSet(value, expected), file, line);
432 }
433 
434 /// Assert that value is not the same set as expected.
435 void shouldNotBeSameSetAs(V, E)(const scope auto ref V value, const scope auto ref E expected, string file = __FILE__, size_t line = __LINE__) {
436     assert_(!isSameSet(value, expected), file, line);
437 }
438 
439 private bool isSameSet(T, U)(const scope auto ref T t, const scope auto ref U u) {
440     import std.array: array;
441     import std.algorithm: canFind;
442 
443     //sort makes the element types have to implement opCmp
444     //instead, try one by one
445     auto ta = t.array;
446     auto ua = u.array;
447     if (ta.length != ua.length) return false;
448     foreach(element; ta)
449     {
450         if (!ua.canFind(element)) return false;
451     }
452 
453     return true;
454 }
455 
456 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter)
457 void shouldBeSameJsonAs(in string actual,
458                         in string expected,
459                         string file = __FILE__,
460                         size_t line = __LINE__)
461     @trusted // not @safe pure due to parseJSON
462 {
463     import std.json: parseJSON, JSONException;
464 
465     auto parse(in string str) {
466         try
467             return str.parseJSON;
468         catch(JSONException ex) {
469             assert_(false, "Failed to parse " ~ str, file, line);
470         }
471         assert(0);
472     }
473 
474     assert_(parse(actual) == parse(expected), file, line);
475 }
476 
477 
478 private void assert_(in bool value, string file, size_t line) @safe pure {
479     assert_(value, "Assertion failure", file, line);
480 }
481 
482 private void assert_(bool value, string message, string file, size_t line) @safe pure {
483     if(!value)
484         throw new Exception(message, file, line);
485 }
486 
487 void fail(in string output, string file, size_t line) @safe pure {
488     assert_(false, output, file, line);
489 }
490 
491 
492 auto should(E)(lazy E expr) {
493 
494     struct Should {
495 
496         bool opEquals(U)(auto ref U other,
497                          string file = __FILE__,
498                          size_t line = __LINE__)
499         {
500             expr.shouldEqual(other, file, line);
501             return true;
502         }
503     }
504 
505     return Should();
506 }