1 /++ DES TestSuite 2 + 3 + Minimal test suite for easy unittesting 4 +/ 5 module des.ts; 6 7 public import std.exception; 8 9 import std.traits; 10 import std.meta; 11 import std.math; 12 import std.range; 13 import std.uni; 14 import std.string; 15 import std.stdio : stderr; 16 import std.conv : to; 17 18 import core.exception : AssertError; 19 20 /// check equals `a` and `b` 21 bool eq(A,B)( A a, B b ) 22 { 23 alias isNum = std.traits.isNumeric; 24 static if( allSatisfy!(isElementArray,A,B) ) 25 { 26 if( a.length != b.length ) return false; 27 foreach( i; 0 .. a.length ) 28 if( !eq( a[i], b[i] ) ) return false; 29 return true; 30 } 31 else static if( allSatisfy!(isSomeString,A,B) ) 32 { 33 if( a.walkLength != b.walkLength ) return false; 34 foreach( x,y; lockstep( a.byGrapheme, b.byGrapheme ) ) 35 if( x != y ) return false; 36 return true; 37 } 38 else static if( isSomeObject!A && isSomeObject!B ) return a is b; 39 else static if( allSatisfy!(isNum,A,B) && anySatisfy!(isFloatingPoint,A,B) ) 40 { 41 static if( isFloatingPoint!A && isFloatingPoint!B ) 42 auto epsilon = fmax( A.epsilon, B.epsilon ); 43 else static if( isFloatingPoint!A ) auto epsilon = A.epsilon; 44 else static if( isFloatingPoint!B ) auto epsilon = B.epsilon; 45 else static assert(0, "WTF? not A nor B isn't floating point" ); 46 47 return abs( a - b ) < epsilon; 48 } 49 else static if( is(typeof(a==b)) ) return a == b; 50 else static assert( 0, format( "uncompatible types '%s' and '%s'", nameOf!A, nameOf!B ) ); 51 } 52 53 /// 54 unittest 55 { 56 assert( eq( 1, 1.0 ) ); 57 assert( eq( [1,2,3], [1.0,2,3] ) ); 58 assert( eq( [1.0f,2,3], [1.0,2,3] ) ); 59 assert( eq( [1,2,3], iota(1,4) ) ); 60 assert( !eq( [1.0000001,2,3], [1,2,3] ) ); 61 assert( eq( [[1,2],[3,4]], [[1.0f,2],[3.0f,4]] ) ); 62 assert( !eq( [[1,2],[3,4]], [[1.1f,2],[3.0f,4]] ) ); 63 assert( !eq( [[1,2],[3,4]], [[1.0f,2],[3.0f]] ) ); 64 } 65 66 /// 67 unittest 68 { 69 // string from Jonathan M Davis presentation 70 auto s1 = `さいごの果実 / ミツバチと科学者`; 71 auto s2 = `さいごの果実 / ミツバチと科学者`w; 72 73 assert( s1.length != s2.length ); 74 assert( !eq( cast(void[])s1, cast(void[])s2 ) ); 75 assert( eq( s1, s2 ) ); 76 77 auto s1a = `さいごの果実`; 78 auto s1b = `ミツバチと科学者`; 79 80 auto s2a = `さいごの果実`w; 81 auto s2b = `ミツバチと科学者`w; 82 83 assert( eq( [s1a,s1b], [s2a,s2b] ) ); 84 85 assert( !eq( "hello", [1,2,3] ) ); 86 assert( eq( " "w, [ 32 ] ) ); 87 static assert( !__traits(compiles, eq(["hello"],1)) ); 88 static assert( !__traits(compiles, eq(["hello"],[1,2,3])) ); 89 } 90 91 /++ check equals `a` and `b` approx with epsilon 92 + Params: 93 + 94 + a = first value 95 + b = second value 96 + eps = numeric epsilon 97 +/ 98 bool eq_approx(A,B,E)( A a, B b, E eps ) 99 if( isNumeric!E ) 100 { 101 static if( allSatisfy!(isElementArray,A,B) ) 102 { 103 if( a.length != b.length ) return false; 104 foreach( i; 0 .. a.length ) 105 if( !eq_approx( a[i], b[i], eps ) ) return false; 106 return true; 107 } 108 else static if( allSatisfy!(isNumeric,A,B) ) return abs( a - b ) < eps; 109 else static assert( 0, format( "uncompatible types '%s' and '%s'", nameOf!A, nameOf!B ) ); 110 } 111 112 /// 113 unittest 114 { 115 assert( eq_approx( [1.1f,2,3], [1,2,3], 0.2 ) ); 116 assert( !eq_approx( [1.1f,2,3], [1,2,3], 0.1 ) ); 117 assert( !eq_approx( [1.0f,2], [1,2,3], 1 ) ); 118 static assert( !__traits(compiles, eq_approx( [[1,2]], [1,2] )) ); 119 } 120 121 /++ 122 + 123 + Params: 124 + 125 + a = first value 126 + b = second value 127 + fmt = error message format, must have two string places `'%s'` for `a` and `b` 128 +/ 129 void assertEq(A,B)( A a, B b, 130 string fmt="assertEq fails: %s != %s", 131 string file=__FILE__, size_t line=__LINE__ ) 132 { 133 if( eq( a, b ) ) return; 134 error( file, line, fmt, toStringForce(a), toStringForce(b) ); 135 } 136 137 /++ throws `AssertError` if `eq( a, b )` 138 + 139 + Params: 140 + 141 + a = first value 142 + b = second value 143 + fmt = error message format, must have two string places `'%s'` for `a` and `b` 144 +/ 145 void assertNotEq(A,B)( A a, B b, 146 lazy string fmt="assertNotEq fails: %s == %s", 147 string file=__FILE__, size_t line=__LINE__ ) 148 { 149 if( !eq( a, b ) ) return; 150 error( file, line, fmt, toStringForce(a), toStringForce(b) ); 151 } 152 153 /++ throws `AssertError` if `a !is null` 154 + 155 + Params: 156 + 157 + a = value 158 + fmt = error message format, must have one string place `'%s'` for `a` 159 +/ 160 void assertNull(A)( A a, 161 lazy string fmt="assertNull fails: %s !is null", 162 string file=__FILE__, size_t line=__LINE__ ) 163 { 164 if( a is null ) return; 165 error( file, line, fmt, toStringForce(a) ); 166 } 167 168 /++ throws `AssertError` if `a is null` 169 + 170 + Params: 171 + 172 + a = value 173 + fmt = error message (without format chars) 174 +/ 175 void assertNotNull(A)( A a, 176 lazy string fmt="assertNotNull fails: value is null", 177 string file=__FILE__, size_t line=__LINE__ ) 178 { 179 if( a !is null ) return; 180 error( file, line, fmt ); 181 } 182 183 /++ throws `AssertError` if `!eq_approx( a, b )` 184 + 185 + Params: 186 + 187 + a = first value 188 + b = second value 189 + eps = epsilon 190 + fmt = error message format, must have two string places `'%s'` for `a` and `b` 191 +/ 192 void assertEqApprox(A,B,E)( A a, B b, E eps, 193 lazy string fmt="assertEqApprox fails: %s != %s", 194 string file=__FILE__, size_t line=__LINE__ ) 195 { 196 if( eq_approx( a, b, eps ) ) return; 197 error( file, line, fmt, toStringForce(a), toStringForce(b) ); 198 } 199 200 /++ throws `AssertError` if tested value out of range 201 + 202 + for RANGETYPE allows values: `"[]"`, `"(]"`, `"[)"`, `"()"` 203 + 204 +/ 205 void assertInRange( string RANGETYPE="[)",MIN,V,MAX,string file=__FILE__,size_t line=__LINE__ ) 206 ( in MIN min_value, in V tested_value, in MAX max_value, 207 lazy string fmt="assertInRange fails: %s is out of %s" ) 208 if( is(typeof(min_value<tested_value)) && is(typeof(tested_value<max_value)) ) 209 { 210 static if( !( RANGETYPE == "[]" || RANGETYPE == "()" || 211 RANGETYPE == "[)" || RANGETYPE == "(]" ) ) 212 static assert( 0, format( "range type must be one of '[]'," ~ 213 "'()', '(]', '[)', not '%s'", RANGETYPE ) ); 214 215 enum op1 = RANGETYPE[0] == '[' ? "<=" : "<"; 216 enum op2 = RANGETYPE[1] == ']' ? "<=" : "<"; 217 218 mixin( format( q{ 219 if( min_value %s tested_value && tested_value %s max_value ) return; 220 error( file, line, fmt, toStringForce(tested_value), format( "%s%%s, %%s%s", min_value, max_value ) ); 221 }, op1, op2, RANGETYPE[0], RANGETYPE[1] ) ); 222 } 223 224 /// 225 unittest 226 { 227 assertInRange( 0, 1, 2 ); 228 assertInRange( 0, 0, 2 ); 229 assertInRange!"[]"( 0, 2, 2 ); 230 assertInRange!"(]"( 0.0f, 2, 2.0 ); 231 } 232 233 void error(Args...)( string file, size_t line, string fmt, Args args ) 234 { 235 string msg; 236 try msg = format( fmt, args ); 237 catch( Exception ) msg = "[FMTERR] " ~ toStringForce( args ); 238 throw new AssertError( msg, file, line ); 239 } 240 241 /+ not pure because to!string isn't pure +/ 242 private string toStringForce(Args...)( in Args args ) 243 { 244 static if( Args.length == 1 ) 245 { 246 alias T = Args[0]; 247 auto val = args[0]; 248 249 static if( isSomeString!T ) 250 return "'" ~ to!string(val) ~ "'"; 251 else static if( is( typeof( to!string( val ) )) ) 252 return to!string( val ); 253 else static if( isElementArray!T ) 254 { 255 string[] rr; 256 foreach( i; 0 .. val.length ) 257 rr ~= toStringForce( val[i] ); 258 return "[ " ~ rr.join(", ") ~ " ]"; 259 } 260 else static if( isSomeObject!T ) 261 { 262 if( val is null ) return "null"; 263 else return to!string( cast(void*)val ); 264 } 265 else static if( is( T == typeof(null) ) ) return null; 266 else return val.stringof; 267 } 268 else return toStringForce( args[0] ) ~ ", " ~ toStringForce( args[1..$] ); 269 } 270 271 unittest 272 { 273 assert( eq( toStringForce([0,4]), "[0, 4]" ) ); 274 assert( eq( toStringForce(null), "null" ) ); 275 assert( eq( toStringForce(0), "0" ) ); 276 277 assert( eq( toStringForce("hello"), "'hello'" ) ); 278 279 Object a = null; 280 281 assert( eq( toStringForce(a), "null" ) ); 282 assert( eq( toStringForce(a,5), "null, 5" ) ); 283 284 assert( eq( to!string(new Object), "object.Object" ) ); 285 assert( !eq( toStringForce(new Object), "object.Object" ) ); 286 } 287 288 unittest 289 { 290 static struct fMtr { float[][] data; alias data this; } 291 static struct dMtr { double[][] data; alias data this; } 292 293 auto a = fMtr( [[1,2,3],[2,3,4]] ); 294 assertEq( a, [[1,2,3],[2,3,4]] ); 295 assertEq( [[1,2,3],[2,3,4]], a ); 296 auto b = fMtr( [[1,2,3],[2,3,4]] ); 297 assertEq( a, b ); 298 auto c = dMtr( [[1,2,3],[2,3,4]] ); 299 assertEq( a, c ); 300 } 301 302 unittest 303 { 304 static struct fMtr { float[][] data; alias data this; } 305 static struct dMtr { double[][] data; alias data this; } 306 307 auto a = fMtr( [[1,2,3],[2,3,4]] ); 308 assertEqApprox( a, [[1,2,3],[2,3,4]], float.epsilon ); 309 assertEqApprox( [[1,2,3],[2,3,4]], a, float.epsilon ); 310 auto b = fMtr( [[1,2,3],[2,3,4]] ); 311 assertEqApprox( a, b, float.epsilon ); 312 auto c = dMtr( [[1,2,3],[2,3,4]] ); 313 assertEqApprox( a, c, float.epsilon ); 314 } 315 316 unittest 317 { 318 static struct fVec { float[3] data; alias data this; } 319 auto a = fVec([1,2,3]); 320 assertEq( a, [1,2,3] ); 321 double[] b = [1,2,3]; 322 assertEq( a, b ); 323 } 324 325 unittest 326 { 327 static assert( !__traits(compiles, eq( "hello", 123 ) ) ); 328 static struct fVec { float[] data; alias data this; } 329 auto a = fVec([32]); 330 static assert( !__traits(compiles, eq( a, 123 ) ) ); 331 } 332 333 private: 334 335 template nameOf(T) 336 { 337 static if( __traits(compiles,typeid(T).name) ) 338 enum nameOf = typeid(T).name; 339 else enum nameOf = T.stringof; 340 } 341 342 // not in des.stdx.traits for break dependencies 343 template isElementArray(R) 344 { 345 enum isElementArray = is(typeof( 346 (inout int = 0) 347 { 348 R r = R.init; 349 auto e = r[0]; 350 static assert( hasLength!R ); 351 static assert( !isNarrowString!R ); 352 })); 353 } 354 355 unittest 356 { 357 static assert( isElementArray!(int[]) ); 358 static assert( isElementArray!(int[3]) ); 359 static assert( isElementArray!(float[]) ); 360 361 static struct Vec0 362 { 363 float[] data; 364 alias data this; 365 } 366 static assert( isElementArray!Vec0 ); 367 368 static struct Vec1 { float[] data; } 369 static assert( !isElementArray!Vec1 ); 370 371 static assert( !isElementArray!(string) ); 372 static assert( !isElementArray!(wstring) ); 373 static assert( !isElementArray!int ); 374 static assert( !isElementArray!float ); 375 static assert( !isElementArray!(immutable(void)[]) ); 376 } 377 378 template isSomeObject(T) 379 { enum isSomeObject = is( T == class ) || is( T == interface ); }