1 module unit_threaded.check; 2 3 import std.exception; 4 import std.conv; 5 import std.algorithm; 6 import std.traits; 7 8 public import unit_threaded.attrs; 9 10 class UnitTestException: Exception { 11 this(in string msgLines[], in string file, in ulong line) { 12 import std.array; 13 super(msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n")); 14 } 15 16 private: 17 18 string getOutputPrefix(in string file, in ulong line) { 19 return " " ~ file ~ ":" ~ line.to!string ~ " - "; 20 } 21 } 22 23 void checkTrue(E)(lazy E condition, in string file = __FILE__, in ulong line = __LINE__) { 24 if(!condition) failEqual(condition, true, file, line); 25 } 26 27 void checkFalse(E)(lazy E condition, in string file = __FILE__, in ulong line = __LINE__) { 28 if(condition) failEqual(condition, false, file, line); 29 } 30 31 void checkEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__) 32 if(is(typeof(value != expected) == bool) && !is(T == class)) { 33 if(value != expected) failEqual(value, expected, file, line); 34 } 35 36 void checkEqual(T)(in T value, in T expected, in string file = __FILE__, in ulong line = __LINE__) 37 if(is(T == class)) { 38 if(value.tupleof != expected.tupleof) failEqual(value, expected, file, line); 39 } 40 41 42 void checkNotEqual(T, U)(in T value, in U expected, in string file = __FILE__, in ulong line = __LINE__) 43 if(is(typeof(value == expected) == bool)) { 44 if(value == expected) { 45 auto valueStr = value.to!string; 46 static if(is(T == string)) { 47 valueStr = `"` ~ valueStr ~ `"`; 48 } 49 auto expectedStr = expected.to!string; 50 static if(is(U == string)) { 51 expectedStr = `"` ~ expectedStr ~ `"`; 52 } 53 54 const msg = "Value " ~ valueStr ~ " is not supposed to be equal to " ~ expectedStr ~ "\n"; 55 throw new UnitTestException([msg], file, line); 56 } 57 } 58 59 void checkNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) { 60 if(value !is null) fail("Value is null", file, line); 61 } 62 63 void checkNotNull(T)(in T value, in string file = __FILE__, in ulong line = __LINE__) { 64 if(value is null) fail("Value is null", file, line); 65 } 66 67 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 68 if(isAssociativeArray!U) 69 { 70 if(value !in container) { 71 fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file, line); 72 } 73 } 74 75 void checkIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 76 if(!isAssociativeArray!U) 77 { 78 if(!find(container, value)) { 79 fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file, line); 80 } 81 } 82 83 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 84 if(isAssociativeArray!U) 85 { 86 if(value in container) { 87 fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file, line); 88 } 89 } 90 91 void checkNotIn(T, U)(in T value, in U container, in string file = __FILE__, in ulong line = __LINE__) 92 if(!isAssociativeArray!U) 93 { 94 if(find(container, value).length > 0) { 95 fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file, line); 96 } 97 } 98 99 void checkThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) { 100 if(!threw!T(expr)) fail("Expression did not throw", file, line); 101 } 102 103 void checkNotThrown(T: Throwable = Exception, E)(lazy E expr, in string file = __FILE__, in ulong line = __LINE__) { 104 if(threw!T(expr)) fail("Expression threw", file, line); 105 } 106 107 private bool threw(T: Throwable, E)(lazy E expr) { 108 try { 109 expr(); 110 } catch(T e) { 111 return true; 112 } 113 114 return false; 115 } 116 117 118 void utFail(in string output, in string file, in ulong line) { 119 fail(output, file, line); 120 } 121 122 private void fail(in string output, in string file, in ulong line) { 123 throw new UnitTestException([output], file, line); 124 } 125 126 private void failEqual(T, U)(in T value, in U expected, in string file, in ulong line) { 127 static if(isArray!T && !isSomeString!T) { 128 const msg = formatArray("Expected: ", expected) ~ formatArray(" Got: ", value); 129 } else { 130 const msg = ["Expected: " ~ formatValue(expected), 131 " Got: " ~ formatValue(value)]; 132 } 133 134 throw new UnitTestException(msg, file, line); 135 } 136 137 private string[] formatArray(T)(in string prefix, in T value) if(isArray!T) { 138 import std.range; 139 auto defaultLines = [prefix ~ value.to!string]; 140 141 static if(!isArray!(ElementType!T)) return defaultLines; 142 else { 143 const maxElementSize = value.empty ? 0 : value.map!(a => a.length).reduce!max; 144 const tooBigForOneLine = (value.length > 5 && maxElementSize > 5) || 145 maxElementSize > 10; 146 if(!tooBigForOneLine) return defaultLines; 147 return [prefix ~ "["] ~ value.map!(a => " " ~ formatValue(a) ~ ",").array ~ " ]"; 148 } 149 } 150 151 private auto formatValue(T)(T element) { 152 static if(isSomeString!T) { 153 return `"` ~ element.to!string ~ `"`; 154 } else { 155 return element.to!string; 156 } 157 } 158 159 160 private void assertCheck(E)(lazy E expression) { 161 assertNotThrown!UnitTestException(expression); 162 } 163 164 unittest { 165 assertCheck(checkTrue(true)); 166 assertCheck(checkFalse(false)); 167 } 168 169 170 unittest { 171 assertCheck(checkEqual(true, true)); 172 assertCheck(checkEqual(false, false)); 173 assertCheck(checkNotEqual(true, false)); 174 175 assertCheck(checkEqual(1, 1)); 176 assertCheck(checkNotEqual(1, 2)); 177 178 assertCheck(checkEqual("foo", "foo")); 179 assertCheck(checkNotEqual("f", "b")); 180 181 assertCheck(checkEqual(1.0, 1.0)); 182 assertCheck(checkNotEqual(1.0, 2.0)); 183 184 assertCheck(checkEqual([2, 3], [2, 3])); 185 assertCheck(checkNotEqual([2, 3], [2, 3, 4])); 186 187 int[] ints = [ 1, 2, 3]; 188 byte[] bytes = [ 1, 2, 3]; 189 byte[] bytes2 = [ 1, 2, 4]; 190 assertCheck(checkEqual(ints, bytes)); 191 assertCheck(checkEqual(bytes, ints)); 192 assertCheck(checkNotEqual(ints, bytes2)); 193 194 assertCheck(checkEqual([1: 2.0, 2: 4.0], [1: 2.0, 2: 4.0])); 195 assertCheck(checkNotEqual([1: 2.0, 2: 4.0], [1: 2.2, 2: 4.0])); 196 const constIntToInts = [ 1:2, 3: 7, 9: 345]; 197 auto intToInts = [ 1:2, 3: 7, 9: 345]; 198 assertCheck(checkEqual(intToInts, constIntToInts)); 199 assertCheck(checkEqual(constIntToInts, intToInts)); 200 } 201 202 unittest { 203 assertCheck(checkNull(null)); 204 class Foo { } 205 assertCheck(checkNotNull(new Foo)); 206 } 207 208 unittest { 209 assertCheck(checkIn(4, [1, 2, 4])); 210 assertCheck(checkNotIn(3.5, [1.1, 2.2, 4.4])); 211 assertCheck(checkIn("foo", ["foo": 1])); 212 assertCheck(checkNotIn(1.0, [2.0: 1, 3.0: 2])); 213 }