1 /++ DES TestSuite 2 + 3 + Minimal test suite for easy unittesting 4 +/ 5 module des.ts; 6 7 import std.traits; 8 import std.typetuple; 9 import std.math; 10 11 import std.stdio; 12 import std.string; 13 import std.exception; 14 import std.conv : to; 15 import std.format : FormatException; 16 import core.exception : AssertError; 17 18 private 19 { 20 debug enum __DEBUG__ = true; 21 else enum __DEBUG__ = false; 22 23 version(unittest) enum __UNITTEST__ = true; 24 else enum __UNITTEST__ = false; 25 26 version( des_ts_always_assert ) 27 enum __ALWAYS_ASSERT__ = true; 28 else 29 { 30 enum __ALWAYS_ASSERT__ = false; 31 32 static if( !__DEBUG__ && !__UNITTEST__ ) 33 pragma(msg, "## Warning: des.ts not use asserts, use 'version=des_ts_always_assert'"); 34 } 35 36 enum __USE_ASSERT__ = __UNITTEST__ || __DEBUG__ || __ALWAYS_ASSERT__; 37 } 38 39 /// check equals `a` and `b` 40 bool eq(A,B)( in A a, in B b ) pure 41 { 42 static if( allSatisfy!(isLikeArray,A,B) ) 43 { 44 if( a.length != b.length ) return false; 45 46 foreach( i; 0 .. a.length ) 47 if( !eq( a[i], b[i] ) ) return false; 48 49 return true; 50 } 51 else static if( isSomeObject!A && isSomeObject!B ) return a is b; 52 else static if( allSatisfy!(isNumeric,A,B) && anySatisfy!(isFloatingPoint,A,B) ) 53 { 54 static if( isFloatingPoint!A && isFloatingPoint!B ) 55 auto epsilon = fmax( A.epsilon, B.epsilon ); 56 else static if( isFloatingPoint!A ) auto epsilon = A.epsilon; 57 else static if( isFloatingPoint!B ) auto epsilon = B.epsilon; 58 else static assert(0, "WTF? not A nor B isn't floating point" ); 59 60 return abs( a - b ) < epsilon; 61 } 62 else return a == b; 63 } 64 65 /// 66 unittest 67 { 68 assert( eq( 1, 1.0 ) ); 69 assert( eq( "hello", "hello"w ) ); 70 assert( !eq( cast(void[])"hello", cast(void[])"hello"w ) ); 71 assert( eq( cast(void[])"hello", cast(void[])"hello" ) ); 72 assert( eq( cast(void[])"hello", "hello" ) ); 73 assert( !eq( cast(void[])"hello", "hello"w ) ); 74 assert( eq( [[1,2],[3,4]], [[1.0f,2],[3.0f,4]] ) ); 75 assert( !eq( [[1,2],[3,4]], [[1.1f,2],[3.0f,4]] ) ); 76 assert( !eq( [[1,2],[3,4]], [[1.0f,2],[3.0f]] ) ); 77 assert( eq( [1,2,3], [1.0,2,3] ) ); 78 assert( eq( [1.0f,2,3], [1.0,2,3] ) ); 79 assert( eq( [1,2,3], [1,2,3] ) ); 80 assert( !eq( [1.0000001,2,3], [1,2,3] ) ); 81 assert( eq( ["hello","world"], ["hello","world"] ) ); 82 assert( !eq( "hello", [1,2,3] ) ); 83 static assert( !__traits(compiles, eq(["hello"],1)) ); 84 static assert( !__traits(compiles, eq(["hello"],[1,2,3])) ); 85 } 86 87 /++ check equals `a` and `b` approx with epsilon 88 + Params: 89 + 90 + a = first value 91 + b = second value 92 + eps = numeric epsilon 93 +/ 94 bool eq_approx(A,B,E)( in A a, in B b, in E eps ) pure 95 if( isNumeric!E && ( allSatisfy!(isNumeric,A,B) || allSatisfy!(isLikeArray,A,B) ) ) 96 { 97 static if( allSatisfy!(isLikeArray,A,B) ) 98 { 99 if( a.length != b.length ) return false; 100 foreach( i; 0 .. a.length ) 101 if( !eq_approx( a[i], b[i], eps ) ) return false; 102 return true; 103 } 104 else return abs( a - b ) < eps; 105 } 106 107 /// 108 unittest 109 { 110 assert( eq_approx( [1.1f,2,3], [1,2,3], 0.2 ) ); 111 assert( !eq_approx( [1.1f,2,3], [1,2,3], 0.1 ) ); 112 assert( !eq_approx( [1.0f,2], [1,2,3], 1 ) ); 113 } 114 115 private template isLikeArray(T) 116 { 117 enum isLikeArray = !is( Unqual!T == void[] ) && 118 is( typeof(T.init[0]) ) && 119 !is( Unqual!(typeof(T.init[0])) == void ) && 120 is( typeof( T.init.length ) == size_t ); 121 } 122 123 unittest 124 { 125 static assert( isLikeArray!(int[]) ); 126 static assert( isLikeArray!(float[]) ); 127 static assert( isLikeArray!(string) ); 128 static assert( !isLikeArray!int ); 129 static assert( !isLikeArray!float ); 130 static assert( !isLikeArray!(immutable(void)[]) ); 131 } 132 133 private template isSomeObject(T) 134 { 135 enum isSomeObject = is( T == class ) || is( T == interface ); 136 } 137 138 /++ try call delegate 139 + 140 + Params: 141 + 142 + fnc = called delegate 143 + throw_unexpected = if `true` when catch exception with type != `E` throw it out, if `false` ignore it 144 + 145 + Returns: 146 + `true` if is catched exception of type `E`, `false` otherwise 147 +/ 148 bool mustExcept(E:Throwable=Exception)( void delegate() fnc, bool throw_unexpected=true ) 149 in { assert( fnc !is null, "delegate is null" ); } body 150 { 151 static if( !is( E == Throwable ) ) 152 { 153 try fnc(); 154 catch( E e ) return true; 155 catch( Throwable t ) 156 if( throw_unexpected ) throw t; 157 return false; 158 } 159 else 160 { 161 try fnc(); 162 catch( Throwable t ) return true; 163 return false; 164 } 165 } 166 167 /// 168 unittest 169 { 170 assert( mustExcept!Exception( { throw new Exception("test"); } ) ); 171 assert( !mustExcept!Exception( { throw new Throwable("test"); }, false ) ); 172 assert( mustExcept( { throw new Exception("test"); } ) ); 173 assert( !mustExcept( { throw new Throwable("test"); }, false ) ); 174 assert( mustExcept!Throwable( { throw new Exception("test"); } ) ); 175 assert( mustExcept!Throwable( { throw new Throwable("test"); } ) ); 176 assert( !mustExcept!Exception({ auto a = 4; }) ); 177 } 178 179 /// 180 unittest 181 { 182 static class A {} 183 static assert( !__traits(compiles, mustExcept!A({})) ); 184 185 static class TestExceptionA : Exception 186 { this() @safe pure nothrow { super( "" ); } } 187 static class TestExceptionB : Exception 188 { this() @safe pure nothrow { super( "" ); } } 189 static class TestExceptionC : TestExceptionA 190 { this() @safe pure nothrow { super(); } } 191 192 assert( mustExcept!Exception({ throw new TestExceptionA; }) ); 193 assert( mustExcept!Exception({ throw new TestExceptionB; }) ); 194 195 assert( mustExcept!TestExceptionA({ throw new TestExceptionA; }) ); 196 assert( mustExcept!TestExceptionA({ throw new TestExceptionC; }) ); 197 assert( mustExcept!TestExceptionB({ throw new TestExceptionB; }) ); 198 199 assert( !mustExcept!TestExceptionB( { throw new TestExceptionA; }, false ) ); 200 assert( !mustExcept!TestExceptionA( { throw new TestExceptionB; }, false ) ); 201 202 auto test_b_catched = false; 203 try mustExcept!TestExceptionA({ throw new TestExceptionB; }); 204 catch( TestExceptionB ) test_b_catched = true; 205 assert( test_b_catched ); 206 } 207 208 /++ throws `AssertError` if `!eq( a, b )` 209 + 210 + Params: 211 + 212 + a = first value 213 + b = second value 214 + fmt = error message format, must have two string places `'%s'` for `a` and `b` 215 +/ 216 void assertEq(A,B,string file=__FILE__,size_t line=__LINE__)( in A a, in B b, lazy string fmt=null ) 217 if( is( typeof( eq(a,b) ) ) ) 218 { 219 static if( __USE_ASSERT__ ) 220 enforce( eq( a, b ), newError( file, line, 221 ( fmt.length > 0 ? fmt : "assertEq fails: %s != %s" ), 222 toStringForce(a), toStringForce(b) ) ); 223 } 224 225 /// 226 unittest 227 { 228 assert( mustExcept!AssertError({ assertEq( 1, 2 ); }) ); 229 assert( mustExcept!AssertError({ assertEq( [1,2], [2,3] ); }) ); 230 assert( !mustExcept!AssertError({ assertEq( [1,2], [1,2] ); }) ); 231 } 232 233 /++ throws `AssertError` if `eq( a, b )` 234 + 235 + Params: 236 + 237 + a = first value 238 + b = second value 239 + fmt = error message format, must have two string places `'%s'` for `a` and `b` 240 +/ 241 void assertNotEq(A,B,string file=__FILE__,size_t line=__LINE__)( in A a, in B b, lazy string fmt=null ) 242 if( is( typeof( eq(a,b) ) ) ) 243 { 244 static if( __USE_ASSERT__ ) 245 enforce( !eq( a, b ), newError( file, line, 246 ( fmt.length > 0 ? fmt : "assertNotEq fails: %s == %s" ), 247 toStringForce(a), toStringForce(b) ) ); 248 } 249 250 /++ throws `AssertError` if `a !is null` 251 + 252 + Params: 253 + 254 + a = value 255 + fmt = error message format, must have one string place `'%s'` for `a` 256 +/ 257 void assertNull(A,string file=__FILE__,size_t line=__LINE__)( in A a, lazy string fmt=null ) 258 { 259 static if( __USE_ASSERT__ ) 260 enforce( a is null, newError( file, line, 261 ( fmt.length > 0 ? fmt : "assertNull fails: %s !is null" ), 262 toStringForce(a) ) ); 263 } 264 265 /++ throws `AssertError` if `a is null` 266 + 267 + Params: 268 + 269 + a = value 270 + fmt = error message format, must have one string place `'%s'` for `a` 271 +/ 272 void assertNotNull(A,string file=__FILE__,size_t line=__LINE__)( in A a, lazy string fmt=null ) 273 { 274 static if( __USE_ASSERT__ ) 275 enforce( a !is null, newError( file, line, 276 ( fmt.length > 0 ? fmt : "assertNotNull fails: %s is null" ), 277 toStringForce(a) ) ); 278 } 279 280 /+ not pure because using stderr and toStringForce isn't pure +/ 281 auto newError(Args...)( string file, size_t line, string fmt, Args args ) 282 { 283 string msg; 284 try msg = format( fmt, args ); 285 catch( Exception ) 286 { 287 stderr.writefln( "bad error format: '%s' (%s:%d)", fmt, file, line ); 288 msg = toStringForce( args ); 289 } 290 return new AssertError( msg, file, line ); 291 } 292 293 /+ not pure because to!string isn't pure +/ 294 string toStringForce(Args...)( in Args args ) 295 { 296 static if( Args.length == 1 ) 297 { 298 alias T = Args[0]; 299 auto val = args[0]; 300 301 static if( is( typeof( to!string( val ) )) ) 302 return to!string( val ); 303 else static if( isLikeArray!T ) 304 { 305 string[] rr; 306 foreach( i; 0 .. val.length ) 307 rr ~= toStringForce( val[i] ); 308 return "[ " ~ rr.join(", ") ~ " ]"; 309 } 310 else static if( isSomeObject!T ) 311 { 312 if( val is null ) return "null"; 313 else return to!string( cast(void*)val ); 314 } 315 else static if( is( T == typeof(null) ) ) return null; 316 else return val.stringof; 317 } 318 else return toStringForce( args[0] ) ~ ", " ~ toStringForce( args[1..$] ); 319 } 320 321 unittest 322 { 323 assert( eq( toStringForce([0,4]), "[0, 4]" ) ); 324 assert( eq( toStringForce(null), "null" ) ); 325 assert( eq( toStringForce(0), "0" ) ); 326 327 Object a = null; 328 329 assert( eq( toStringForce(a), "null" ) ); 330 assert( eq( toStringForce(a,5), "null, 5" ) ); 331 332 assert( eq( to!string(new Object), "object.Object" ) ); 333 assert( !eq( toStringForce(new Object), "object.Object" ) ); 334 }