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    Dummy version of runTests so "normal" code compiles.
24  */
25 int runTests(T...)(in string[] args) {
26     return runTestsImpl;
27 }
28 
29 /// ditto
30 int runTests(T)(string[] args, T testData) {
31     return runTestsImpl;
32 }
33 
34 int runTestsImpl() {
35     import core.runtime: Runtime;
36     import core.stdc.stdio: printf;
37 
38     try {
39 
40         Runtime.moduleUnitTester();
41 
42         printf("\n");
43         version(Posix)
44             printf("\033[32;1mOk\033[0;;m");
45         else
46             printf("Ok");
47 
48         printf(": All tests passed\n\n");
49 
50         return 0;
51     } catch(Throwable _)
52         return 1;
53 }
54 
55 /**
56    Dummy version so "normal" code compiles
57  */
58 int[] allTestData(T...)() {
59     return [];
60 }
61 
62 /**
63    No-op version of writelnUt
64  */
65 void writelnUt(T...)(auto ref T args) {
66 
67 }
68 
69 /**
70    Same as unit_threaded.property.check
71  */
72 void check(alias F)(int numFuncCalls = 100,
73                     in string file = __FILE__, in size_t line = __LINE__) @trusted {
74     import unit_threaded.property: utCheck = check;
75     utCheck!F(numFuncCalls, file, line);
76 }
77 
78 /**
79    Same as unit_threaded.property.checkCustom
80  */
81 void checkCustom(alias Generator, alias Predicate)
82                 (int numFuncCalls = 100, in string file = __FILE__, in size_t line = __LINE__) @trusted {
83     import unit_threaded.property: utCheckCustom = checkCustom;
84     utCheckCustom!(Generator, Predicate)(numFuncCalls, file, line);
85 }
86 
87 
88 /**
89    Generic output interface
90  */
91 interface Output {
92     void send(in string output) @safe;
93     void flush() @safe;
94 }
95 
96 /**
97    Dummy version of unit_threaded.testcase.TestCase
98  */
99 class TestCase {
100     abstract void test();
101     void setup() {}
102     void shutdown() {}
103     static TestCase currentTest() { return new class TestCase { override void test() {}}; }
104     Output getWriter() { return new class Output { override void send(in string output) {} override void flush() {}}; }
105 }
106 
107 
108 /**
109    Same as unit_threaded.mock.mock
110  */
111 auto mock(T)() {
112     import unit_threaded.mock: utMock = mock;
113     return utMock!T;
114 }
115 
116 /**
117    Same as unit_threaded.mock.mockStruct
118  */
119 auto mockStruct(T...)(auto ref T returns) {
120     import unit_threaded.mock: utMockStruct = mockStruct;
121     return utMockStruct(returns);
122 }
123 
124 /**
125    Throw if condition is not true.
126  */
127 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) {
128     assert_(cast(bool)condition(), file, line);
129 }
130 
131 /// Throw if condition not false.
132 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) {
133     assert_(!cast(bool)condition(), file, line);
134 }
135 
136 /// Assert value is equal to expected
137 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
138 
139     void checkInputRange(T)(auto ref const(T) _) @trusted {
140         auto obj = cast(T)_;
141         bool e = obj.empty;
142         auto f = obj.front;
143         obj.popFront;
144     }
145     enum isInputRange(T) = is(T: Elt[], Elt) || is(typeof(checkInputRange(T.init)));
146 
147     static if(is(V == class)) {
148         assert_(value.tupleof == expected.tupleof, file, line);
149     } else static if(isInputRange!V && isInputRange!E) {
150         auto ref unqual(T)(auto ref const(T) obj) @trusted {
151             static if(is(T == void[]))
152                 return cast(ubyte[])obj;
153             else
154                 return cast(T)obj;
155         }
156         import std.algorithm: equal;
157         assert_(equal(unqual(value), unqual(expected)), file, line);
158     } else {
159         assert_(cast(const)value == cast(const)expected, file, line);
160     }
161 }
162 
163 /// Assert value is not equal to expected.
164 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
165     assert_(value != expected, file, line);
166 }
167 
168 /// Assert value is null.
169 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
170     assert_(value is null, file, line);
171 }
172 
173 /// Assert value is not null
174 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
175     assert_(value !is null, file, line);
176 }
177 
178 enum isLikeAssociativeArray(T, K) = is(typeof({
179     if(K.init in T) { }
180     if(K.init !in T) { }
181 }));
182 static assert(isLikeAssociativeArray!(string[string], string));
183 static assert(!isLikeAssociativeArray!(string[string], int));
184 
185 
186 /// Assert that value is in container.
187 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
188     if(isLikeAssociativeArray!(U, T)) {
189     assert_(cast(bool)(value in container), file, line);
190 }
191 
192 /// ditto.
193 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
194     if (!isLikeAssociativeArray!(U, T))
195 {
196     import std.algorithm: find;
197     import std.array: empty;
198     assert_(!find(container, value).empty, file, line);
199 }
200 
201 /// Assert value is not in container.
202 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
203     if(isLikeAssociativeArray!U) {
204     assert_(!cast(bool)(value in container), file, line);
205 }
206 
207 /// ditto.
208 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
209     if (!isLikeAssociativeArray!(U, T))
210 {
211     import std.algorithm: find;
212     import std.array: empty;
213     assert_(find(container, value).empty, file, line);
214 }
215 
216 /// Assert that expr throws.
217 void shouldThrow(T : Throwable = Exception, E)
218                 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
219     auto threw = false;
220     () @trusted {
221         try {
222             expr();
223         } catch(T _) {
224             threw = true;
225         }
226     }();
227     assert_(threw, file, line);
228 }
229 
230 /// Assert that expr throws an Exception that must have the type E, derived types won't do.
231 void shouldThrowExactly(T : Throwable = Exception, E)
232                        (lazy E expr, in string file = __FILE__, in size_t line = __LINE__)
233 {
234     T throwable = null;
235 
236     () @trusted {
237         try {
238             expr();
239             assert_(false, file, line);
240         } catch(T t) {
241             throwable = t;
242         }
243     }();
244 
245     //Object.opEquals is @system and impure
246     const sameType = () @trusted { return throwable !is null && typeid(throwable) == typeid(T); }();
247     assert_(sameType, file, line);
248 
249 }
250 
251 /// Assert that expr doesn't throw
252 void shouldNotThrow(T: Throwable = Exception, E)
253                    (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
254     () @trusted {
255         try
256             expr();
257         catch(T _)
258             assert_(false, file, line);
259     }();
260 }
261 
262 /// Assert that expr throws and the exception message is msg.
263 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
264                                                           string msg,
265                                                           string file = __FILE__,
266                                                           size_t line = __LINE__) {
267     T throwable = null;
268 
269     () @trusted {
270         try {
271             expr();
272         } catch(T ex) {
273             throwable = ex;
274         }
275     }();
276 
277     assert_(throwable !is null && throwable.msg == msg, file, line);
278 }
279 
280 /// Assert that value is approximately equal to expected.
281 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, size_t line = __LINE__) {
282     import std.math: approxEqual;
283     assert_(approxEqual(value, expected), file, line);
284 }
285 
286 /// assert that rng is empty.
287 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
288     import std.range: isInputRange;
289     import std.traits: isAssociativeArray;
290     import std.array;
291 
292     static if(isInputRange!R)
293         assert_(rng.empty, file, line);
294     else static if(isAssociativeArray!R)
295         () @trusted { assert_(rng.keys.empty, file, line); }();
296     else
297         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
298 }
299 
300 /// Assert that rng is not empty.
301 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
302     import std.range: isInputRange;
303     import std.traits: isAssociativeArray;
304     import std.array;
305 
306     static if(isInputRange!R)
307         assert_(!rnd.empty, file, line);
308     else static if(isAssociativeArray!R)
309         () @trusted { assert_(!rng.keys.empty, file, line); }();
310     else
311         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
312 }
313 
314 /// Assert that t should be greater than u.
315 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
316                                in string file = __FILE__, in size_t line = __LINE__)
317 {
318     assert_(t > u, file, line);
319 }
320 
321 /// Assert that t should be smaller than u.
322 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
323                                in string file = __FILE__, in size_t line = __LINE__)
324 {
325     assert_(t < u, file, line);
326 }
327 
328 /// Assert that value is the same set as expected (i.e. order doesn't matter)
329 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
330     assert_(isSameSet(value, expected), file, line);
331 }
332 
333 /// Assert that value is not the same set as expected.
334 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
335     assert_(!isSameSet(value, expected), file, line);
336 }
337 
338 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) {
339     import std.array: array;
340     import std.algorithm: canFind;
341 
342     //sort makes the element types have to implement opCmp
343     //instead, try one by one
344     auto ta = t.array;
345     auto ua = u.array;
346     if (ta.length != ua.length) return false;
347     foreach(element; ta)
348     {
349         if (!ua.canFind(element)) return false;
350     }
351 
352     return true;
353 }
354 
355 /// Assert that actual and expected represent the same JSON (i.e. formatting doesn't matter)
356 void shouldBeSameJsonAs(in string actual,
357                         in string expected,
358                         in string file = __FILE__,
359                         in size_t line = __LINE__)
360     @trusted // not @safe pure due to parseJSON
361 {
362     import std.json: parseJSON, JSONException;
363 
364     auto parse(in string str) {
365         try
366             return str.parseJSON;
367         catch(JSONException ex) {
368             assert_(false, "Failed to parse " ~ str, file, line);
369         }
370         assert(0);
371     }
372 
373     assert_(parse(actual) == parse(expected), file, line);
374 }
375 
376 
377 private void assert_(in bool value, in string file, in size_t line) @safe pure {
378     assert_(value, "Assertion failure", file, line);
379 }
380 
381 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure {
382     if(!value)
383         throw new Exception(message, file, line);
384 }
385 
386 void fail(in string output, in string file, in size_t line) @safe pure {
387     assert_(false, output, file, line);
388 }