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)) {
31     if(value != expected) failEqual(value, expected, file, line);
32 }
33 
34 void checkNotEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__)
35 if(is(typeof(value == expected) == bool)) {
36     if(value == expected) failEqual(value, expected, file, line);
37 }
38 
39 void checkNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) {
40     if(value !is null) fail(getOutputPrefix(file, line) ~ "Value is null");
41 }
42 
43 void checkNotNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) {
44     if(value is null) fail(getOutputPrefix(file, line) ~ "Value is null");
45 }
46 
47 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
48     if(isAssociativeArray!U)
49 {
50     if(value !in container) {
51         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " not in " ~ to!string(container));
52     }
53 }
54 
55 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
56     if(!isAssociativeArray!U)
57 {
58     if(!find(container, value)) {
59         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " not in " ~ to!string(container));
60     }
61 }
62 
63 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
64     if(isAssociativeArray!U)
65 {
66     if(value in container) {
67         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " in " ~ to!string(container));
68     }
69 }
70 
71 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__)
72     if(!isAssociativeArray!U)
73 {
74     if(find(container, value).length > 0) {
75         fail(getOutputPrefix(file, line) ~ "Value " ~ to!string(value) ~ " in " ~ to!string(container));
76     }
77 }
78 
79 void checkThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) {
80     if(!threw!T(expr)) fail(getOutputPrefix(file, line) ~ "Expression did not throw");
81 }
82 
83 void checkNotThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) {
84     if(threw!T(expr)) fail(getOutputPrefix(file, line) ~ "Expression threw");
85 }
86 
87 private bool threw(T: Throwable, E)(lazy E expr) {
88     try {
89         expr();
90     } catch(T e) {
91         return true;
92     }
93 
94     return false;
95 }
96 
97 
98 void utFail(in string output, in string file, in ulong line) {
99     fail(getOutputPrefix(file, line) ~ output);
100 }
101 
102 private void fail(in string output) pure {
103     throw new UnitTestException(output);
104 }
105 
106 private void failEqual(T, U)(in T value, in U expected, in string file, in ulong line) {
107     throw new UnitTestException(getOutput(value, expected, file, line));
108 }
109 
110 private string getOutput(T, U)(in T value, in U expected, in string file, in ulong line) {
111     return getOutputPrefix(file, line) ~
112         "Value " ~ to!string(value) ~
113         " is not the expected " ~ to!string(expected) ~ "\n";
114 }
115 
116 private string getOutputPrefix(in string file, in ulong line) {
117     return "    " ~ file ~ ":" ~ to!string(line) ~ " - ";
118 }
119 
120 
121 private void assertCheck(E)(lazy E expression) {
122     assertNotThrown!UnitTestException(expression);
123 }
124 
125 unittest {
126     assertCheck(checkTrue(true));
127     assertCheck(checkFalse(false));
128 }
129 
130 
131 unittest {
132     assertCheck(checkEqual(true, true));
133     assertCheck(checkEqual(false, false));
134     assertCheck(checkNotEqual(true, false));
135 
136     assertCheck(checkEqual(1, 1));
137     assertCheck(checkNotEqual(1, 2));
138 
139     assertCheck(checkEqual("foo", "foo"));
140     assertCheck(checkNotEqual("f", "b"));
141 
142     assertCheck(checkEqual(1.0, 1.0));
143     assertCheck(checkNotEqual(1.0, 2.0));
144 
145     assertCheck(checkEqual([2, 3], [2, 3]));
146     assertCheck(checkNotEqual([2, 3], [2, 3, 4]));
147 
148     int[] ints = [ 1, 2, 3];
149     byte[] bytes = [ 1, 2, 3];
150     byte[] bytes2 = [ 1, 2, 4];
151     assertCheck(checkEqual(ints, bytes));
152     assertCheck(checkEqual(bytes, ints));
153     assertCheck(checkNotEqual(ints, bytes2));
154 
155     assertCheck(checkEqual([1: 2.0, 2: 4.0], [1: 2.0, 2: 4.0]));
156     assertCheck(checkNotEqual([1: 2.0, 2: 4.0], [1: 2.2, 2: 4.0]));
157     const constIntToInts = [ 1:2, 3: 7, 9: 345];
158     auto intToInts = [ 1:2, 3: 7, 9: 345];
159     assertCheck(checkEqual(intToInts, constIntToInts));
160     assertCheck(checkEqual(constIntToInts, intToInts));
161 }
162 
163 unittest {
164     assertCheck(checkNull(null));
165     class Foo { }
166     assertCheck(checkNotNull(new Foo));
167 }
168 
169 unittest {
170     assertCheck(checkIn(4, [1, 2, 4]));
171     assertCheck(checkNotIn(3.5, [1.1, 2.2, 4.4]));
172     assertCheck(checkIn("foo", ["foo": 1]));
173     assertCheck(checkNotIn(1.0, [2.0: 1, 3.0: 2]));
174 }