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