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                     in string file = __FILE__, in 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, in string file = __FILE__, in 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, in string file = __FILE__, in 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, in string file = __FILE__, in 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, in string file = __FILE__, in 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)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
207     assert_(value != expected, file, line);
208 }
209 
210 /// Assert value is null.
211 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
212     assert_(value is null, file, line);
213 }
214 
215 /// Assert value is not null
216 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in 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)(in auto ref T value, in auto ref U container, in string file = __FILE__, in 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)(in auto ref T value, U container, in string file = __FILE__, in 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)(in auto ref T value, in auto ref U container, in string file = __FILE__, in 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)(in auto ref T value, U container, in string file = __FILE__, in 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, in string file = __FILE__, in size_t line = __LINE__) {
261     import std.traits: isSafe, isUnsafe;
262 
263     auto threw = false;
264 
265     static if(isUnsafe!expr)
266         void callExpr() @system { expr(); }
267     else
268         void callExpr() @safe   { expr(); }
269 
270     bool impl() {
271         try {
272             callExpr;
273             return false;
274         } catch(T _) {
275             return true;
276         }
277     }
278 
279     static if(isSafe!callExpr)
280         threw = () @trusted { return impl; }();
281     else
282         threw = impl;
283 
284     assert_(threw, file, line);
285 }
286 
287 /// Assert that expr throws an Exception that must have the type E, derived types won't do.
288 void shouldThrowExactly(T : Throwable = Exception, E)
289                        (lazy E expr, in string file = __FILE__, in size_t line = __LINE__)
290 {
291     import std.traits: isSafe, isUnsafe;
292 
293     T throwable = null;
294 
295     static if(isUnsafe!expr)
296         void callExpr() @system { expr(); }
297     else
298         void callExpr() @safe   { expr(); }
299 
300     void impl() {
301         try
302             callExpr;
303         catch(T t) {
304             throwable = t;
305         }
306     }
307 
308     static if(isSafe!callExpr)
309         () @trusted { return impl; }();
310     else
311         impl;
312 
313     //Object.opEquals is @system and impure
314     const sameType = () @trusted { return throwable !is null && typeid(throwable) == typeid(T); }();
315     assert_(sameType, file, line);
316 }
317 
318 
319 /// Assert that expr doesn't throw
320 void shouldNotThrow(T: Throwable = Exception, E)
321                    (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
322 
323     import std.traits: isSafe, isUnsafe;
324 
325     static if(isUnsafe!expr)
326         void callExpr() @system { expr(); }
327     else
328         void callExpr() @safe   { expr(); }
329 
330     void impl() {
331         try
332             callExpr;
333         catch(T t) {
334             assert_(false, file, line);
335         }
336     }
337 
338     static if(isSafe!callExpr)
339         () @trusted { return impl; }();
340     else
341         impl;
342 }
343 
344 /// Assert that expr throws and the exception message is msg.
345 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
346                                                           string msg,
347                                                           string file = __FILE__,
348                                                           size_t line = __LINE__) {
349     import std.traits: isSafe, isUnsafe;
350 
351     T throwable = null;
352 
353     static if(isUnsafe!expr)
354         void callExpr() @system { expr(); }
355     else
356         void callExpr() @safe   { expr(); }
357 
358     void impl() {
359         try
360             callExpr;
361         catch(T t) {
362             throwable = t;
363         }
364     }
365 
366     static if(isSafe!callExpr)
367         () @trusted { return impl; }();
368     else
369         impl;
370 
371     assert_(throwable !is null && throwable.msg == msg, file, line);
372 }
373 
374 /// Assert that value is approximately equal to expected.
375 void shouldApproxEqual(V, E)
376                       (in V value, in E expected, double maxRelDiff = 1e-2, double maxAbsDiff = 1e-5, string file = __FILE__, size_t line = __LINE__)
377 {
378     import std.math: approxEqual;
379     assert_(approxEqual(value, expected, maxRelDiff, maxAbsDiff), file, line);
380 }
381 
382 /// assert that rng is empty.
383 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
384     import std.range: isInputRange;
385     import std.traits: isAssociativeArray;
386     import std.array;
387 
388     static if(isInputRange!R)
389         assert_(rng.empty, file, line);
390     else static if(isAssociativeArray!R)
391         () @trusted { assert_(rng.keys.empty, file, line); }();
392     else
393         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
394 }
395 
396 /// Assert that rng is not empty.
397 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
398     import std.range: isInputRange;
399     import std.traits: isAssociativeArray;
400     import std.array;
401 
402     static if(isInputRange!R)
403         assert_(!rnd.empty, file, line);
404     else static if(isAssociativeArray!R)
405         () @trusted { assert_(!rng.keys.empty, file, line); }();
406     else
407         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
408 }
409 
410 /// Assert that t should be greater than u.
411 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
412                                in string file = __FILE__, in size_t line = __LINE__)
413 {
414     assert_(t > u, file, line);
415 }
416 
417 /// Assert that t should be smaller than u.
418 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
419                                in string file = __FILE__, in size_t line = __LINE__)
420 {
421     assert_(t < u, file, line);
422 }
423 
424 /// Assert that value is the same set as expected (i.e. order doesn't matter)
425 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
426     assert_(isSameSet(value, expected), file, line);
427 }
428 
429 /// Assert that value is not the same set as expected.
430 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
431     assert_(!isSameSet(value, expected), file, line);
432 }
433 
434 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) {
435     import std.array: array;
436     import std.algorithm: canFind;
437 
438     //sort makes the element types have to implement opCmp
439     //instead, try one by one
440     auto ta = t.array;
441     auto ua = u.array;
442     if (ta.length != ua.length) return false;
443     foreach(element; ta)
444     {
445         if (!ua.canFind(element)) return false;
446     }
447 
448     return true;
449 }
450 
451 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter)
452 void shouldBeSameJsonAs(in string actual,
453                         in string expected,
454                         in string file = __FILE__,
455                         in size_t line = __LINE__)
456     @trusted // not @safe pure due to parseJSON
457 {
458     import std.json: parseJSON, JSONException;
459 
460     auto parse(in string str) {
461         try
462             return str.parseJSON;
463         catch(JSONException ex) {
464             assert_(false, "Failed to parse " ~ str, file, line);
465         }
466         assert(0);
467     }
468 
469     assert_(parse(actual) == parse(expected), file, line);
470 }
471 
472 
473 private void assert_(in bool value, in string file, in size_t line) @safe pure {
474     assert_(value, "Assertion failure", file, line);
475 }
476 
477 private void assert_(bool value, in string message, in string file, in size_t line) @safe pure {
478     if(!value)
479         throw new Exception(message, file, line);
480 }
481 
482 void fail(in string output, in string file, in size_t line) @safe pure {
483     assert_(false, output, file, line);
484 }
485 
486 
487 auto should(E)(lazy E expr) {
488 
489     struct Should {
490 
491         bool opEquals(U)(auto ref U other,
492                          in string file = __FILE__,
493                          in size_t line = __LINE__)
494         {
495             expr.shouldEqual(other, file, line);
496             return true;
497         }
498     }
499 
500     return Should();
501 }