1 module unit_threaded.check; 2 3 import std.exception; 4 import std.conv; 5 import std.algorithm; 6 import std.traits; 7 import std.range; 8 9 public import unit_threaded.attrs; 10 11 @safe: 12 13 class UnitTestException: Exception { 14 this(in string[] msgLines, in string file, in ulong line) { 15 import std.array; 16 super(msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n")); 17 } 18 19 private: 20 21 string getOutputPrefix(in string file, in ulong line) const { 22 return " " ~ file ~ ":" ~ line.to!string ~ " - "; 23 } 24 } 25 26 void checkTrue(E)(lazy E condition, in string file = __FILE__, in ulong line = __LINE__) { 27 if(!condition) failEqual(condition, true, file, line); 28 } 29 30 void checkFalse(E)(lazy E condition, in string file = __FILE__, in ulong line = __LINE__) { 31 if(condition) failEqual(condition, false, file, line); 32 } 33 34 35 void checkEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__) 36 if(is(typeof(value != expected) == bool) && !is(T == class) && !is(T == interface)) { 37 if(value != expected) failEqual(value, expected, file, line); 38 } 39 40 void checkEqual(T)(in T value, in T expected, in string file = __FILE__, in ulong line = __LINE__) 41 if(is(T == class)) { 42 if(value.tupleof != expected.tupleof) failEqual(value, expected, file, line); 43 } 44 45 void checkEqual(T)(in T value, in T expected, in string file = __FILE__, in ulong line = __LINE__) 46 if(is(T == interface)) { 47 48 //not allowed to compare interfaces or take their addresses in @safe code 49 () @trusted { if(value != expected) failEqual(value, expected, file, line); }(); 50 } 51 52 53 54 //@trusted because of object.opEquals 55 void checkNotEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__) @trusted 56 if(is(typeof(value == expected) == bool)) { 57 if(value == expected) { 58 auto valueStr = value.to!string; 59 static if(is(T == string)) { 60 valueStr = `"` ~ valueStr ~ `"`; 61 } 62 auto expectedStr = expected.to!string; 63 static if(is(U == string)) { 64 expectedStr = `"` ~ expectedStr ~ `"`; 65 } 66 67 const msg = "Value " ~ valueStr ~ " is not supposed to be equal to " ~ expectedStr ~ "\n"; 68 throw new UnitTestException([msg], file, line); 69 } 70 } 71 72 void checkNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) { 73 if(value !is null) fail("Value is null", file, line); 74 } 75 76 void checkNotNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) { 77 if(value is null) fail("Value is null", file, line); 78 } 79 80 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 81 if(isAssociativeArray!U) 82 { 83 if(value !in container) { 84 fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file, line); 85 } 86 } 87 88 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 89 if(!isAssociativeArray!U) 90 { 91 if(!find(container, value)) { 92 fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file, line); 93 } 94 } 95 96 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 97 if(isAssociativeArray!U) 98 { 99 if(value in container) { 100 fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file, line); 101 } 102 } 103 104 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 105 if(!isAssociativeArray!U) 106 { 107 if(find(container, value).length > 0) { 108 fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file, line); 109 } 110 } 111 112 void checkThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) { 113 if(!threw!T(expr)) fail("Expression did not throw", file, line); 114 } 115 116 void checkNotThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) { 117 if(threw!T(expr)) fail("Expression threw", file, line); 118 } 119 120 private bool threw(T: Throwable, E)(lazy E expr) { 121 try { 122 expr(); 123 } catch(T e) { 124 return true; 125 } 126 127 return false; 128 } 129 130 131 void utFail(in string output, in string file, in ulong line) { 132 fail(output, file, line); 133 } 134 135 private void fail(in string output, in string file, in ulong line) { 136 throw new UnitTestException([output], file, line); 137 } 138 139 private void failEqual(T, U)(in T value, in U expected, in string file, in ulong line) { 140 static if(isArray!T && !isSomeString!T) { 141 const msg = formatArray("Expected: ", expected) ~ formatArray(" Got: ", value); 142 } else { 143 const msg = ["Expected: " ~ formatValue(expected), 144 " Got: " ~ formatValue(value)]; 145 } 146 147 throw new UnitTestException(msg, file, line); 148 } 149 150 private string[] formatArray(T)(in string prefix, in T value) if(isArray!T) { 151 import std.range; 152 auto defaultLines = [prefix ~ value.to!string]; 153 154 static if(!isArray!(ElementType!T)) return defaultLines; 155 else { 156 const maxElementSize = value.empty ? 0 : value.map!(a => a.length).reduce!max; 157 const tooBigForOneLine = (value.length > 5 && maxElementSize > 5) || 158 maxElementSize > 10; 159 if(!tooBigForOneLine) return defaultLines; 160 return [prefix ~ "["] ~ value.map!(a => " " ~ formatValue(a) ~ ",").array ~ " ]"; 161 } 162 } 163 164 private auto formatValue(T)(T element) { 165 static if(isSomeString!T) { 166 return `"` ~ element.to!string ~ `"`; 167 } else { 168 return () @trusted { return element.to!string; }(); 169 } 170 } 171 172 173 private void assertCheck(E)(lazy E expression) { 174 assertNotThrown!UnitTestException(expression); 175 } 176 177 private void assertFail(E)(lazy E expression) { 178 assertThrown!UnitTestException(expression); 179 } 180 181 182 unittest { 183 assertCheck(checkTrue(true)); 184 assertCheck(checkFalse(false)); 185 } 186 187 188 unittest { 189 assertCheck(checkEqual(true, true)); 190 assertCheck(checkEqual(false, false)); 191 assertCheck(checkNotEqual(true, false)); 192 193 assertCheck(checkEqual(1, 1)); 194 assertCheck(checkNotEqual(1, 2)); 195 196 assertCheck(checkEqual("foo", "foo")); 197 assertCheck(checkNotEqual("f", "b")); 198 199 assertCheck(checkEqual(1.0, 1.0)); 200 assertCheck(checkNotEqual(1.0, 2.0)); 201 202 assertCheck(checkEqual([2, 3], [2, 3])); 203 assertCheck(checkNotEqual([2, 3], [2, 3, 4])); 204 205 int[] ints = [ 1, 2, 3]; 206 byte[] bytes = [ 1, 2, 3]; 207 byte[] bytes2 = [ 1, 2, 4]; 208 assertCheck(checkEqual(ints, bytes)); 209 assertCheck(checkEqual(bytes, ints)); 210 assertCheck(checkNotEqual(ints, bytes2)); 211 212 assertCheck(checkEqual([1: 2.0, 2: 4.0], [1: 2.0, 2: 4.0])); 213 assertCheck(checkNotEqual([1: 2.0, 2: 4.0], [1: 2.2, 2: 4.0])); 214 const constIntToInts = [ 1:2, 3: 7, 9: 345]; 215 auto intToInts = [ 1:2, 3: 7, 9: 345]; 216 assertCheck(checkEqual(intToInts, constIntToInts)); 217 assertCheck(checkEqual(constIntToInts, intToInts)); 218 } 219 220 unittest { 221 assertCheck(checkNull(null)); 222 class Foo { } 223 assertCheck(checkNotNull(new Foo)); 224 } 225 226 unittest { 227 assertCheck(checkIn(4, [1, 2, 4])); 228 assertCheck(checkNotIn(3.5, [1.1, 2.2, 4.4])); 229 assertCheck(checkIn("foo", ["foo": 1])); 230 assertCheck(checkNotIn(1.0, [2.0: 1, 3.0: 2])); 231 } 232 233 234 void checkEmpty(R)(R rng, in string file = __FILE__, in ulong line = __LINE__) if(isInputRange!R) { 235 if(!rng.empty) fail("Range not empty", file, line); 236 } 237 238 void checkEmpty(T)(in T aa, in string file = __FILE__, in ulong line = __LINE__) if(isAssociativeArray!T) { 239 //keys is @system 240 () @trusted { if(!aa.keys.empty) fail("AA not empty", file, line); }(); 241 } 242 243 244 void checkNotEmpty(R)(R rng, in string file = __FILE__, in ulong line = __LINE__) if(isInputRange!R) { 245 if(rng.empty) fail("Range empty", file, line); 246 } 247 248 249 void checkNotEmpty(T)(in T aa, in string file = __FILE__, in ulong line = __LINE__) if(isAssociativeArray!T) { 250 //keys is @system 251 () @trusted { if(aa.keys.empty) fail("AA empty", file, line); }(); 252 } 253 254 255 unittest { 256 int[] ints; 257 string[] strings; 258 string[string] aa; 259 260 assertCheck(checkEmpty(ints)); 261 assertCheck(checkEmpty(strings)); 262 assertCheck(checkEmpty(aa)); 263 264 assertFail(checkNotEmpty(ints)); 265 assertFail(checkNotEmpty(strings)); 266 assertFail(checkNotEmpty(aa)); 267 268 269 ints ~= 1; 270 strings ~= "foo"; 271 aa["foo"] = "bar"; 272 273 assertCheck(checkNotEmpty(ints)); 274 assertCheck(checkNotEmpty(strings)); 275 assertCheck(checkNotEmpty(aa)); 276 277 assertFail(checkEmpty(ints)); 278 assertFail(checkEmpty(strings)); 279 assertFail(checkEmpty(aa)); 280 } 281 282 283 void checkGreaterThan(T, U)(in T t, in U u, in string file = __FILE__, in ulong line = __LINE__) { 284 if(t <= u) fail(text(t, " is not > ", u), file, line); 285 } 286 287 void checkSmallerThan(T, U)(in T t, in U u, in string file = __FILE__, in ulong line = __LINE__) { 288 if(t >= u) fail(text(t, " is not < ", u), file, line); 289 } 290 291 unittest { 292 assertCheck(checkGreaterThan(7, 5)); 293 assertFail(checkGreaterThan(5, 7)); 294 assertFail(checkGreaterThan(7, 7)); 295 296 assertCheck(checkSmallerThan(5, 7)); 297 assertFail(checkSmallerThan(7, 5)); 298 assertFail(checkSmallerThan(7, 7)); 299 }