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     static if(is(V == class)) {
95         assert_(value.tupleof == expected.tupleof, file, line);
96     } else static if(!__traits(compiles, value == expected)) {
97         import std.algorithm: equal;
98         assert_(equal(value, expected), file, line);
99     } else {
100         assert_(value == expected, file, line);
101     }
102 }
103 
104 void shouldNotEqual(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
105     assert_(value != expected, file, line);
106 }
107 
108 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
109     assert_(value is null, file, line);
110 }
111 
112 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) {
113     assert_(value !is null, file, line);
114 }
115 
116 enum isLikeAssociativeArray(T, K) = is(typeof({
117     if(K.init in T) { }
118     if(K.init !in T) { }
119 }));
120 static assert(isLikeAssociativeArray!(string[string], string));
121 static assert(!isLikeAssociativeArray!(string[string], int));
122 
123 
124 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
125     if(isLikeAssociativeArray!U) {
126     assert_(cast(bool)(value in container), file, line);
127 }
128 
129 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
130     if (!isLikeAssociativeArray!(U, T))
131 {
132     import std.algorithm: find;
133     import std.array: empty;
134     assert_(!find(container, value).empty, file, line);
135 }
136 
137 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
138     if(isLikeAssociativeArray!U) {
139     assert_(!cast(bool)(value in container), file, line);
140 }
141 
142 void shouldNotBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
143     if (!isLikeAssociativeArray!(U, T))
144 {
145     import std.algorithm: find;
146     import std.array: empty;
147     assert_(find(container, value).empty, file, line);
148 }
149 
150 void shouldThrow(T : Throwable = Exception, E)
151                 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
152     () @trusted {
153         try {
154             expr();
155             assert_(false, file, line);
156         } catch(T _) {
157 
158         }
159     }();
160 }
161 
162 void shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr,
163     in string file = __FILE__, in size_t line = __LINE__)
164 {
165     () @trusted {
166         try {
167             expr();
168             assert_(false, file, line);
169         } catch(T _) {
170             //Object.opEquals is @system and impure
171             const sameType = () @trusted { return threw.typeInfo == typeid(T); }();
172             assert_(sameType, file, line);
173         }
174     }();
175 }
176 
177 void shouldNotThrow(T: Throwable = Exception, E)
178                    (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) {
179     () @trusted {
180         try
181             expr();
182         catch(T _)
183             assert_(false, file, line);
184     }();
185 }
186 
187 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
188                                                           string msg,
189                                                           string file = __FILE__,
190                                                           size_t line = __LINE__) {
191     () @trusted {
192         try {
193             expr();
194             assert_(false, file, line);
195         } catch(T ex) {
196             assert_(ex.msg == msg, file, line);
197         }
198     }();
199 }
200 
201 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, size_t line = __LINE__) {
202     import std.math: approxEqual;
203     assert_(approxEqual(value, expected), file, line);
204 }
205 
206 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
207     import std.range: isInputRange;
208     import std.traits: isAssociativeArray;
209     import std.array;
210 
211     static if(isInputRange!R)
212         assert_(rng.empty, file, line);
213     else static if(isAssociativeArray!R)
214         () @trusted { assert_(rng.keys.empty, file, line); }();
215     else
216         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
217 }
218 
219 void shouldNotBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) {
220     import std.range: isInputRange;
221     import std.traits: isAssociativeArray;
222     import std.array;
223 
224     static if(isInputRange!R)
225         assert_(!rnd.empty, file, line);
226     else static if(isAssociativeArray!R)
227         () @trusted { assert_(!rng.keys.empty, file, line); }();
228     else
229         static assert(false, "Cannot call shouldBeEmpty on " ~ R.stringof);
230 }
231 
232 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
233                                in string file = __FILE__, in size_t line = __LINE__)
234 {
235     assert_(t > u, file, line);
236 }
237 
238 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
239                                in string file = __FILE__, in size_t line = __LINE__)
240 {
241     assert_(t < u, file, line);
242 }
243 
244 void shouldBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
245     assert_(isSameSet(value, expected), file, line);
246 }
247 
248 void shouldNotBeSameSetAs(V, E)(in auto ref V value, in auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) {
249     assert_(!isSameSet(value, expected), file, line);
250 }
251 
252 private bool isSameSet(T, U)(in auto ref T t, in auto ref U u) {
253     import std.array: array;
254     import std.algorithm: canFind;
255 
256     //sort makes the element types have to implement opCmp
257     //instead, try one by one
258     auto ta = t.array;
259     auto ua = u.array;
260     if (ta.length != ua.length) return false;
261     foreach(element; ta)
262     {
263         if (!ua.canFind(element)) return false;
264     }
265 
266     return true;
267 }
268 
269 void shouldBeSameJsonAs(in string actual,
270                         in string expected,
271                         in string file = __FILE__,
272                         in size_t line = __LINE__)
273     @trusted // not @safe pure due to parseJSON
274 {
275     import std.json: parseJSON, JSONException;
276 
277     auto parse(in string str) {
278         try
279             return str.parseJSON;
280         catch(JSONException ex) {
281             assert_(false, "Failed to parse " ~ str, file, line);
282         }
283         assert(0);
284     }
285 
286     assert_(parse(actual) == parse(expected), file, line);
287 }
288 
289 
290 private void assert_(in bool value, in string file, in size_t line) @safe pure {
291     assert_(value, "Assertion failure", file, line);
292 }
293 
294 private void assert_(bool value, in string message, in string file, in size_t line) @trusted pure {
295     if(!value)
296         throw new Exception(message, file, line);
297 }
298 
299 void fail(in string output, in string file, in size_t line) @safe pure {
300     assert_(false, output, file, line);
301 }