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 }