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 }