1 module exceptionhandling; 2 3 private { 4 import std.math : approxEqual; 5 bool cmpFloat(T)(T tt, T tc) { 6 return approxEqual(tt, tc); 7 } 8 9 bool cmpFloatNot(T)(T tt, T tc) { 10 return !approxEqual(tt, tc); 11 } 12 13 bool cmpNot(T)(T tt, T tc) { 14 return tt != tc; 15 } 16 17 bool cmp(T)(T tt, T tc) { 18 return tt == tc; 19 } 20 } 21 22 /** Assert that `toTest` is equal to `toCompareAgainst`. 23 If `T` is a floating point `approxEqual` is used to compare the values. 24 `toTest` is returned if the comparision is correct. 25 If the comparision is incorrect an Exception is thrown. If assertEqual is used 26 in a unittest block an AssertError is thrown an Exception otherwise. 27 */ 28 ref T assertEqual(T)(auto ref T toTest, auto ref T toCompareAgainst, 29 const string file = __FILE__, const int line = __LINE__) 30 { 31 import std.traits : isFloatingPoint; 32 static if(isFloatingPoint!T) { 33 return AssertImpl!(T, cmpFloat)(toTest, toCompareAgainst, file, line); 34 } else { 35 return AssertImpl!(T, cmp)(toTest, toCompareAgainst, file, line); 36 } 37 } 38 39 /// ditto 40 ref T assertNotEqual(T)(auto ref T toTest, auto ref T toCompareAgainst, 41 const string file = __FILE__, const int line = __LINE__) 42 { 43 import std.traits : isFloatingPoint; 44 static if(isFloatingPoint!T) { 45 return AssertImpl!(T, cmpFloatNot)(toTest, toCompareAgainst, file, line); 46 } else { 47 return AssertImpl!(T, cmpNot)(toTest, toCompareAgainst, file, line); 48 } 49 } 50 51 private ref T AssertImpl(T,alias Cmp)(auto ref T toTest, auto ref T toCompareAgainst, 52 const string file, const int line) 53 { 54 import std.format : format; 55 version(unittest) { 56 import core.exception : AssertError; 57 alias ExceptionType = AssertError; 58 } else { 59 alias ExceptionType = Exception; 60 } 61 62 bool cmpRslt = false; 63 try { 64 cmpRslt = Cmp(toTest, toCompareAgainst); 65 } catch(ExceptionType e) { 66 throw new ExceptionType( 67 format("Exception thrown while \"toTest(%s) != toCompareAgainst(%s)\"", 68 toTest, toCompareAgainst), file, line, e 69 ); 70 } 71 72 if(!cmpRslt) { 73 throw new ExceptionType(format("toTest(%s) != toCompareAgainst(%s)", 74 toTest, toCompareAgainst), file, line 75 ); 76 } 77 return toTest; 78 } 79 80 unittest { 81 import core.exception : AssertError; 82 import std.exception : assertThrown; 83 cast(void)assertEqual(1.0, 1.0); 84 cast(void)assertNotEqual(1.0, 0.0); 85 86 assertThrown!AssertError(assertEqual(1.0, 0.0)); 87 88 cast(void)assertEqual(1, 1); 89 cast(void)assertNotEqual(1, 0); 90 } 91 92 unittest { 93 import core.exception : AssertError; 94 import std.exception : assertThrown; 95 96 class Foo { 97 int a; 98 this(int a) { this.a = a; } 99 override bool opEquals(Object o) { 100 Foo f = cast(Foo)o; 101 return f.a == this.a; 102 } 103 } 104 105 auto f = new Foo(1); 106 107 assertThrown!AssertError(assertEqual(f, null)); 108 } 109 110 /** Calls `exp` if `exp` does not throw the return value from `exp` is 111 returned, if `exp` throws the Exception is cought, a new Exception is 112 constructed with a message made of `args` space seperated and the previously 113 cought exception is nested in the newly created exception. 114 */ 115 auto expect(ET = Exception, F, int line = __LINE__, string file = __FILE__, Args...) 116 (lazy F exp, lazy Args args) 117 { 118 try { 119 return exp(); 120 } catch(Exception e) { 121 throw new ET(joinElem(args), file, line, e); 122 } 123 } 124 125 private string joinElem(Args...)(lazy Args args) { 126 import std.array : appender; 127 import std.format : formattedWrite; 128 129 auto app = appender!string(); 130 foreach(arg; args) { 131 formattedWrite(app, "%s ", arg); 132 } 133 return app.data; 134 } 135 136 unittest { 137 import std.string : indexOf; 138 string barMsg = "Fun will thrown, I'm sure"; 139 string funMsg = "Hopefully this is true"; 140 141 void fun() { 142 throw new Exception(funMsg); 143 } 144 145 void bar() { 146 expect(fun(), barMsg); 147 } 148 149 bool didThrow = false; 150 try { 151 bar(); 152 } catch(Exception e) { 153 assert(e.msg.indexOf(barMsg) != -1, "\"" ~ e.msg ~ "\" " ~ barMsg); 154 assert(e.next !is null); 155 assert(e.next.msg.indexOf(funMsg) != -1, e.next.msg); 156 didThrow = true; 157 } 158 159 assert(didThrow); 160 }