1 module unit_threaded.check;
2 
3 @safe
4 
5 import std.exception;
6 import std.conv;
7 import std.algorithm;
8 import std.traits;
9 
10 //attributes
11 enum UnitTest; //opt-in to registration
12 enum DontTest; //opt-out of registration
13 enum HiddenTest; //hide test. Not run by default but can be run.
14 
15 class UnitTestException: Exception {
16     this(string msg) {
17         super(msg);
18     }
19 }
20 
21 void checkTrue(in bool condition, in string file = __FILE__, in ulong line = __LINE__) {
22     if(!condition) failEqual(condition, true, file, line);
23 }
24 
25 void checkFalse(in bool condition, in string file = __FILE__, in ulong line = __LINE__) {
26     if(condition) failEqual(condition, false, file, line);
27 }
28 
29 void checkEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__)
30 if(is(typeof(value != expected) == bool) && !is(T == class)) {
31     if(value != expected) failEqual(value, expected, file, line);
32 }
33 
34 void checkEqual(T)(in T value, in T expected, in string file = __FILE__, in ulong line = __LINE__)
35 if(is(T == class)) {
36     if(value.tupleof != expected.tupleof) failEqual(value, expected, file, line);
37 }
38 
39 
40 void checkNotEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__)
41 if(is(typeof(value == expected) == bool)) {
42     if(value == expected) failEqual(value, expected, file, line);
43 }
44 
45 void checkNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) {
46     if(value !is null) fail(getOutputPrefix(file, line) ~ "Value is null");
47 }
48 
49 void checkNotNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) {
50     if(value is null) fail(getOutputPrefix(file, line) ~ "Value is null");
51 }
52 
53 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
54     if(isAssociativeArray!U)
55 {
56     if(value !in container) {
57         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " not in " ~ to!string(container));
58     }
59 }
60 
61 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
62     if(!isAssociativeArray!U)
63 {
64     if(!find(container, value)) {
65         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " not in " ~ to!string(container));
66     }
67 }
68 
69 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
70     if(isAssociativeArray!U)
71 {
72     if(value in container) {
73         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " in " ~ to!string(container));
74     }
75 }
76 
77 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
78     if(!isAssociativeArray!U)
79 {
80     if(find(container, value).length > 0) {
81         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " in " ~ to!string(container));
82     }
83 }
84 
85 void checkThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) {
86     if(!threw!T(expr)) fail(getOutputPrefix(file, line) ~ "Expression did not throw");
87 }
88 
89 void checkNotThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) {
90     if(threw!T(expr)) fail(getOutputPrefix(file, line) ~ "Expression threw");
91 }
92 
93 private bool threw(T: Throwable, E)(lazy E expr) {
94     try {
95         expr();
96     } catch(T e) {
97         return true;
98     }
99 
100     return false;
101 }
102 
103 
104 void utFail(in string output, in string file, in ulong line) {
105     fail(getOutputPrefix(file, line) ~ output);
106 }
107 
108 private void fail(in string output) {
109     throw new UnitTestException(output);
110 }
111 
112 private void failEqual(T, U)(in T value, in U expected, in string file, in ulong line) {
113     throw new UnitTestException(getOutput(value, expected, file, line));
114 }
115 
116 private string getOutput(T, U)(in T value, in U expected, in string file, in ulong line) {
117     return getOutputPrefix(file, line) ~
118         "Value " ~ to!string(value) ~
119         " is not the expected " ~ to!string(expected) ~ "\n";
120 }
121 
122 private string getOutputPrefix(in string file, in ulong line) {
123     return "    " ~ file ~ ":" ~ to!string(line) ~ " - ";
124 }
125 
126 
127 private void assertCheck(E)(lazy E expression) {
128     assertNotThrown!UnitTestException(expression);
129 }
130 
131 unittest {
132     assertCheck(checkTrue(true));
133     assertCheck(checkFalse(false));
134 }
135 
136 
137 unittest {
138     assertCheck(checkEqual(true, true));
139     assertCheck(checkEqual(false, false));
140     assertCheck(checkNotEqual(true, false));
141 
142     assertCheck(checkEqual(1, 1));
143     assertCheck(checkNotEqual(1, 2));
144 
145     assertCheck(checkEqual("foo", "foo"));
146     assertCheck(checkNotEqual("f", "b"));
147 
148     assertCheck(checkEqual(1.0, 1.0));
149     assertCheck(checkNotEqual(1.0, 2.0));
150 
151     assertCheck(checkEqual([2, 3], [2, 3]));
152     assertCheck(checkNotEqual([2, 3], [2, 3, 4]));
153 
154     int[] ints = [ 1, 2, 3];
155     byte[] bytes = [ 1, 2, 3];
156     byte[] bytes2 = [ 1, 2, 4];
157     assertCheck(checkEqual(ints, bytes));
158     assertCheck(checkEqual(bytes, ints));
159     assertCheck(checkNotEqual(ints, bytes2));
160 
161     assertCheck(checkEqual([1: 2.0, 2: 4.0], [1: 2.0, 2: 4.0]));
162     assertCheck(checkNotEqual([1: 2.0, 2: 4.0], [1: 2.2, 2: 4.0]));
163     const constIntToInts = [ 1:2, 3: 7, 9: 345];
164     auto intToInts = [ 1:2, 3: 7, 9: 345];
165     assertCheck(checkEqual(intToInts, constIntToInts));
166     assertCheck(checkEqual(constIntToInts, intToInts));
167 }
168 
169 unittest {
170     assertCheck(checkNull(null));
171     class Foo { }
172     assertCheck(checkNotNull(new Foo));
173 }
174 
175 unittest {
176     assertCheck(checkIn(4, [1, 2, 4]));
177     assertCheck(checkNotIn(3.5, [1.1, 2.2, 4.4]));
178     assertCheck(checkIn("foo", ["foo": 1]));
179     assertCheck(checkNotIn(1.0, [2.0: 1, 3.0: 2]));
180 }