1 module dshould.basic; 2 3 import std..string : empty; 4 import dshould.ShouldType; 5 public import dshould.ShouldType : should; 6 7 pure @safe unittest 8 { 9 2.should.be(2); 10 2.should.not.be(5); 11 5.should.equal(5); 12 5.should.not.equal(6); 13 5.should.not.be.equal.greater(6); 14 5.should.be.equal.greater(5); 15 5.should.be.equal.greater(4); 16 5.should.not.be.equal.greater(6); 17 5.should.be.smaller(6); 18 } 19 20 unittest 21 { 22 (new Object).should.not.be(new Object); 23 (new Object).should.not.equal(new Object); 24 (new Object).should.not.be(null); 25 (cast(Object) null).should.be(null); 26 27 auto obj = new Object; 28 29 obj.should.equal(obj); 30 obj.should.be(obj); 31 32 class SameyClass 33 { 34 override bool opEquals(Object o) { return true; } 35 } 36 37 (new SameyClass).should.not.be(new SameyClass); 38 (new SameyClass).should.equal(new SameyClass); 39 40 (cast(void delegate()) null).should.be(null); 41 } 42 43 auto not(Should)(Should should) pure 44 if (isInstanceOf!(ShouldType, Should)) 45 { 46 should.allowOnlyWordsBefore!([], "not"); 47 48 return should.addWord!"not"; 49 } 50 51 auto be(Should)(Should should) pure 52 if (isInstanceOf!(ShouldType, Should)) 53 { 54 should.allowOnlyWordsBefore!(["not"], "be"); 55 56 return should.addWord!"be"; 57 } 58 59 void be(Should, T)(Should should, T value, string file = __FILE__, size_t line = __LINE__) 60 if (isInstanceOf!(ShouldType, Should) && !should.hasWord!"approximately") 61 { 62 import std.format : format; 63 64 should.allowOnlyWordsBefore!(["not"], "be"); 65 66 enum isNullType = is(T == typeof(null)); 67 // only types that can have toString need to disambiguate 68 enum isReferenceType = is(T == class) || is(T == interface); 69 70 with (should.addData!"rhs"(value)) 71 { 72 static if (hasWord!"not") 73 { 74 const refInfo = isReferenceType ? "different reference than " : "not "; 75 76 static if (isNullType) 77 { 78 check(data.lhs() !is null, format(": expected non-null, but got null"), file, line); 79 } 80 else 81 { 82 auto lhs = data.lhs(); 83 auto rhs = data.rhs; 84 85 check(lhs !is rhs, format(": expected %s%s, but got %s", refInfo, rhs.quote, lhs.quote), file, line); 86 } 87 } 88 else 89 { 90 const refInfo = isReferenceType ? "same reference as " : ""; 91 auto lhs = data.lhs(); 92 auto rhs = data.rhs; 93 94 static if (is(T == typeof(null))) 95 { 96 check(lhs is null, format(": expected null, but got %s", lhs.quote), file, line); 97 } 98 else 99 { 100 101 check(lhs is rhs, format(": expected %s%s, but got %s", refInfo, rhs.quote, lhs.quote), file, line); 102 } 103 } 104 } 105 } 106 107 auto equal(Should)(Should should) 108 if (isInstanceOf!(ShouldType, Should)) 109 { 110 should.allowOnlyWordsBefore!(["not", "be", "greater", "smaller"], "equal"); 111 112 return should.addWord!"equal"; 113 } 114 115 void equal(Should, T)(Should should, T value, string file = __FILE__, size_t line = __LINE__) 116 if (isInstanceOf!(ShouldType, Should) && !should.hasWord!"approximately") 117 { 118 should.equal.addData!"rhs"(value).numericCheck(file, line); 119 } 120 121 auto greater(Should)(Should should) 122 if (isInstanceOf!(ShouldType, Should)) 123 { 124 should.allowOnlyWordsBefore!(["not", "be", "equal", "smaller"], "greater"); 125 should.requireWord!("be", "greater"); 126 127 return should.addWord!"greater"; 128 } 129 130 void greater(Should, T)(Should should, T value, string file = __FILE__, size_t line = __LINE__) 131 if (isInstanceOf!(ShouldType, Should)) 132 { 133 should.greater.addData!"rhs"(value).numericCheck(file, line); 134 } 135 136 auto smaller(Should)(Should should) 137 if (isInstanceOf!(ShouldType, Should)) 138 { 139 should.allowOnlyWordsBefore!(["not", "be", "equal", "greater"], "smaller"); 140 should.requireWord!("be", "smaller"); 141 142 return should.addWord!"smaller"; 143 } 144 145 void smaller(Should, T)(Should should, T value, string file = __FILE__, size_t line = __LINE__) 146 if (isInstanceOf!(ShouldType, Should)) 147 { 148 should.smaller.addData!"rhs"(value).numericCheck(file, line); 149 } 150 151 void numericCheck(Should)(Should should, string file, size_t line) 152 if (isInstanceOf!(ShouldType, Should)) 153 { 154 import std.format : format; 155 156 enum notPart = should.hasWord!"not" ? "!" : ""; 157 enum equalPart = should.hasWord!"equal" ? "==" : ""; 158 enum equalPartShort = should.hasWord!"equal" ? "=" : ""; 159 enum smallerPart = should.hasWord!"smaller" ? "<" : ""; 160 enum greaterPart = should.hasWord!"greater" ? ">" : ""; 161 162 enum isFloating = __traits(isFloating, typeof(should.data.lhs())); 163 164 static if (isFloating) 165 { 166 enum combinedWithoutEqual = notPart ~ smallerPart ~ greaterPart; 167 } 168 else 169 { 170 enum combinedWithoutEqual = smallerPart ~ greaterPart; 171 } 172 173 enum combined = combinedWithoutEqual ~ (combinedWithoutEqual.empty ? equalPart : equalPartShort); 174 175 static if (isFloating || !should.hasWord!"not") 176 { 177 enum checkString = "%s " ~ combined ~ " %s"; 178 enum message = combined ~ " %s"; 179 } 180 else 181 { 182 enum checkString = "!(%s " ~ combined ~ " %s)"; 183 enum message = "not " ~ combined ~ " %s"; 184 } 185 186 with (should) 187 { 188 auto lhs = data.lhs(); 189 auto rhs = data.rhs; 190 191 check( 192 mixin(format!checkString("lhs", "rhs")), 193 format(": expected value %s, but got %s", message.format(rhs.quote), lhs.quote), 194 file, line 195 ); 196 } 197 } 198 199 /** 200 * This could be in a separate file, say approx.d, 201 * if doing so didn't crash dmd. 202 * see https://issues.dlang.org/show_bug.cgi?id=18839 203 */ 204 unittest 205 { 206 5.should.be.approximately(5.1, 0.11); 207 5.should.approximately(0.11).be(5.1); 208 0.should.approximately(1.1).equal(1.0); 209 0.should.approximately(1.1).equal(-1.0); 210 0.should.not.approximately(0.1).equal(1); 211 42.3.should.be.approximately(42.3, 1e-3); 212 } 213 214 auto approximately(Should)(Should should, double permissibleError) 215 if (isInstanceOf!(ShouldType, Should)) 216 { 217 return should.addWord!"approximately".addData!"permissibleError"(permissibleError); 218 } 219 220 auto approximately(Should)( 221 Should should, double value, double permissibleError, 222 string file = __FILE__, size_t line = __LINE__ 223 ) 224 if (isInstanceOf!(ShouldType, Should)) 225 { 226 static assert( 227 should.hasWord!"be" || should.hasWord!"equal", 228 `bad grammar: expected "be" or "equal" before "approximately"` 229 ); 230 231 should.allowOnlyWordsBefore!(["be", "equal", "not"], "approximately"); 232 233 return should 234 .addWord!"approximately" 235 .addData!"permissibleError"(permissibleError) 236 .addData!"rhs"(value) 237 .approximateCheck(file, line); 238 } 239 240 void be(Should, T)(Should should, T value, string file = __FILE__, size_t line = __LINE__) 241 if (isInstanceOf!(ShouldType, Should) && should.hasWord!"approximately") 242 { 243 import std.traits : isDynamicArray; 244 245 static if (isDynamicArray!T) 246 { 247 pragma(msg, "reference comparison of dynamic array: this is probably not what you want."); 248 } 249 250 should.allowOnlyWordsBefore!(["approximately", "not"], "equal"); 251 252 return should.addData!"rhs"(value).approximateCheck(file, line); 253 } 254 255 void equal(Should, T)(Should should, T value, string file = __FILE__, size_t line = __LINE__) 256 if (isInstanceOf!(ShouldType, Should) && should.hasWord!"approximately") 257 { 258 should.allowOnlyWordsBefore!(["approximately", "not"], "be"); 259 260 return should.addData!"rhs"(value).approximateCheck(file, line); 261 } 262 263 void approximateCheck(Should)(Should should, string file, size_t line) 264 if (isInstanceOf!(ShouldType, Should)) 265 { 266 import std.format : format; 267 import std.math : abs; 268 269 with (should) 270 { 271 auto lhs = data.lhs(); 272 auto rhs = data.rhs; 273 274 static if (hasWord!"not") 275 { 276 check( 277 abs(lhs - rhs) >= data.permissibleError, 278 format(": expected value outside %s ± %s, but got %s", rhs, data.permissibleError, lhs), 279 file, line 280 ); 281 } 282 else 283 { 284 check( 285 abs(lhs - rhs) < data.permissibleError, 286 format(": expected %s ± %s, but got %s", rhs, data.permissibleError, lhs), 287 file, line 288 ); 289 } 290 } 291 } 292 293 private string quote(T)(T t) 294 { 295 import std.format : format; 296 297 static if (is(T: string)) 298 { 299 return format!`'%s'`(t); 300 } 301 else 302 { 303 return format!`%s`(t); 304 } 305 }