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__) @trusted {
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__) @trusted {
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         import std.algorithm: equal;
189         assert_(equal(unqual(value), unqual(expected)), file, line);
190 
191     } else {
192         assert_(cast(const)value == cast(const)expected, file, line);
193     }
194 }
195 
196 
197 
198 /// Assert value is not equal to expected.
199 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
200     assert_(value != expected, file, line);
201 }
202 
203 /// Assert value is null.
204 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
205     assert_(value is null, file, line);
206 }
207 
208 /// Assert value is not null
209 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
210     assert_(value !is null, file, line);
211 }
212 
213 enum isLikeAssociativeArray(T, K) = is(typeof({
214     if(K.init in T) { }
215     if(K.init !in T) { }
216 }));
217 static assert(isLikeAssociativeArray!(string[string], string));
218 static assert(!isLikeAssociativeArray!(string[string], int));
219 
220 
221 /// Assert that value is in container.
222 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
223     if(isLikeAssociativeArray!(U, T)) {
224     assert_(cast(bool)(value in container), file, line);
225 }
226 
227 /// ditto.
228 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
229     if (!isLikeAssociativeArray!(U, T))
230 {
231     import std.algorithm: find;
232     import std.array: empty;
233     assert_(!find(container, value).empty, file, line);
234 }
235 
236 /// Assert value is not in container.
237 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
238     if(isLikeAssociativeArray!U) {
239     assert_(!cast(bool)(value in container), file, line);
240 }
241 
242 /// ditto.
243 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
244     if (!isLikeAssociativeArray!(U, T))
245 {
246     import std.algorithm: find;
247     import std.array: empty;
248     assert_(find(container, value).empty, file, line);
249 }
250 
251 /// Assert that expr throws.
252 void shouldThrow(T : Throwable = Exception, E)
253                 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
254     auto threw = false;
255     () @trusted {
256         try {
257             expr();
258         } catch(T _) {
259             threw = true;
260         }
261     }();
262     assert_(threw, file, line);
263 }
264 
265 /// Assert that expr throws an Exception that must have the type E, derived types won't do.
266 void shouldThrowExactly(T : Throwable = Exception, E)
267                        (lazy E expr, in string file = __FILE__, in size_t line = __LINE__)
268 {
269     T throwable = null;
270 
271     () @trusted {
272         try {
273             expr();
274             assert_(false, file, line);
275         } catch(T t) {
276             throwable = t;
277         }
278     }();
279 
280     //Object.opEquals is @system and impure
281     const sameType = () @trusted { return throwable !is null && typeid(throwable) == typeid(T); }();
282     assert_(sameType, file, line);
283 
284 }
285 
286 /// Assert that expr doesn't throw
287 void shouldNotThrow(T: Throwable = Exception, E)
288                    (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
289     () @trusted {
290         try
291             expr();
292         catch(T _)
293             assert_(false, file, line);
294     }();
295 }
296 
297 /// Assert that expr throws and the exception message is msg.
298 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
299                                                           string msg,
300                                                           string file = __FILE__,
301                                                           size_t line = __LINE__) {
302     T throwable = null;
303 
304     () @trusted {
305         try {
306             expr();
307         } catch(T ex) {
308             throwable = ex;
309         }
310     }();
311 
312     assert_(throwable !is null && throwable.msg == msg, file, line);
313 }
314 
315 /// Assert that value is approximately equal to expected.
316 void shouldApproxEqual(V, E)(in V value, in E expected, double maxRelDiff = 1e-2, double maxAbsDiff = 1e-5, string file = __FILE__, size_t line = __LINE__) {
317     import std.math: approxEqual;
318     assert_(approxEqual(value, expected, maxRelDiff, maxAbsDiff), file, line);
319 }
320 
321 /// assert that rng is empty.
322 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
323     import std.range: isInputRange;
324     import std.traits: isAssociativeArray;
325     import std.array;
326 
327     static if(isInputRange!R)
328         assert_(rng.empty, file, line);
329     else static if(isAssociativeArray!R)
330         () @trusted { assert_(rng.keys.empty, file, line); }();
331     else
332         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
333 }
334 
335 /// Assert that rng is not empty.
336 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
337     import std.range: isInputRange;
338     import std.traits: isAssociativeArray;
339     import std.array;
340 
341     static if(isInputRange!R)
342         assert_(!rnd.empty, file, line);
343     else static if(isAssociativeArray!R)
344         () @trusted { assert_(!rng.keys.empty, file, line); }();
345     else
346         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
347 }
348 
349 /// Assert that t should be greater than u.
350 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
351                                in string file = __FILE__, in size_t line = __LINE__)
352 {
353     assert_(t > u, file, line);
354 }
355 
356 /// Assert that t should be smaller than u.
357 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
358                                in string file = __FILE__, in size_t line = __LINE__)
359 {
360     assert_(t < u, file, line);
361 }
362 
363 /// Assert that value is the same set as expected (i.e. order doesn't matter)
364 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
365     assert_(isSameSet(value, expected), file, line);
366 }
367 
368 /// Assert that value is not the same set as expected.
369 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
370     assert_(!isSameSet(value, expected), file, line);
371 }
372 
373 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) {
374     import std.array: array;
375     import std.algorithm: canFind;
376 
377     //sort makes the element types have to implement opCmp
378     //instead, try one by one
379     auto ta = t.array;
380     auto ua = u.array;
381     if (ta.length != ua.length) return false;
382     foreach(element; ta)
383     {
384         if (!ua.canFind(element)) return false;
385     }
386 
387     return true;
388 }
389 
390 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter)
391 void shouldBeSameJsonAs(in string actual,
392                         in string expected,
393                         in string file = __FILE__,
394                         in size_t line = __LINE__)
395     @trusted // not @safe pure due to parseJSON
396 {
397     import std.json: parseJSON, JSONException;
398 
399     auto parse(in string str) {
400         try
401             return str.parseJSON;
402         catch(JSONException ex) {
403             assert_(false, "Failed to parse " ~ str, file, line);
404         }
405         assert(0);
406     }
407 
408     assert_(parse(actual) == parse(expected), file, line);
409 }
410 
411 
412 private void assert_(in bool value, in string file, in size_t line) @safe pure {
413     assert_(value, "Assertion failure", file, line);
414 }
415 
416 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure {
417     if(!value)
418         throw new Exception(message, file, line);
419 }
420 
421 void fail(in string output, in string file, in size_t line) @safe pure {
422     assert_(false, output, file, line);
423 }