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