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