1 // SemiTwist Library
2 // Written in the D programming language.
3 
4 module semitwist.util.unittests;
5 
6 // deferEnsure requires this to exist in the calling context
7 public import semitwist.util.reflect : _deferAssert_ExprTypeOf = ExprTypeOf;
8 
9 import std.conv;
10 import std.demangle;
11 import std.stdio;
12 import std.traits;
13 
14 import semitwist.util.all;
15 
16 /**
17 Sounds like a contradiction of terms, but this is just
18 intended to allow unittests to output ALL failures instead
19 of only outputting the first one and then stopping.
20 */
21 template deferAssert(string condStr, string msg="")
22 {
23 	enum deferAssert =
24 	// The "_deferAssert_line" is a workaround for DMD Bug #2887
25 	"{ enum long _deferAssert_line = __LINE__;\n"~
26 	"    try\n"~
27 	"    {\n"~
28 	"        bool _deferAssert_condResult = ("~condStr~")?true:false;\n"~
29 	"        _deferAssert!(_deferAssert_line, __FILE__, "~condStr.stringof~", "~msg.stringof~")(_deferAssert_condResult);\n"~
30 	"    }\n"~
31 	"    catch(Throwable _deferAssert_e)\n"~
32 	"        _deferAssertException!(_deferAssert_line, __FILE__, "~condStr.stringof~", "~msg.stringof~")(_deferAssert_e);\n"~
33 	"}\n";
34 }
35 
36 bool _deferAssert(long line, string file, string condStr, string msg="")(bool condResult)
37 {
38 	if(!condResult)
39 	{
40 		assertCount++;
41 		writefln("%s(%s): Assert Failed (%s)%s",
42 		         file, line, condStr,
43 		         msg=="" ? "" : ": " ~ msg);
44 		writeln();
45 	}
46 	
47 	return condResult;
48 }
49 
50 void _deferAssertException(long line, string file, string condStr, string msg="")(Object thrown)
51 {
52 	assertCount++;
53 	writef("%s(%s): Assert Threw (%s)%s:\nThrew: ",
54 	       file, line, condStr,
55 	       msg=="" ? "" : ": " ~ msg);
56 	Exception e = cast(Exception)thrown;
57 	if(e)
58 		writeln(thrown);
59 	else
60 		writefln("Object: type '%s': %s", thrown.classinfo.name, thrown);
61 	writeln();
62 }
63 
64 //TODO: Something like: mixin(blah!(`_1 == (_2 ~ _3)`, `"Hello"`, `"He"`, `"llo"`));
65 
66 template deferEnsure(string value, string condStr, string msg="")
67 {
68 	enum deferEnsure =
69 	// The "_deferAssert_line" is a workaround for DMD Bug #2887
70 	"{ enum long _deferAssert_line = __LINE__;\n"~
71 	"    try\n"~
72 	"    {\n"~
73 	"        auto _ = ("~value~");\n"~
74 	"        bool _deferAssert_condResult = ("~condStr~")?true:false;\n"~
75 	"        _deferEnsure!(_deferAssert_line, __FILE__, "~value.stringof~", "~condStr.stringof~", _deferAssert_ExprTypeOf!(typeof("~value~")), "~msg.stringof~")(_, _deferAssert_condResult);\n"~
76 	"    }\n"~
77 	"    catch(Throwable _deferAssert_e)\n"~
78 	"        _deferEnsureException!(_deferAssert_line, __FILE__, "~value.stringof~", "~condStr.stringof~", "~msg.stringof~")(_deferAssert_e);\n"~
79 	"}\n";
80 }
81 
82 bool _deferEnsure(long line, string file, string valueStr, string condStr, T, string msg="")(T valueResult, bool condResult)
83 {
84 	if(!condResult)
85 	{
86 		assertCount++;
87 		writefln("%s(%s): Ensure Failed%s\n"~
88 		         "Expression '%s':\n"~
89 		         "Expected: %s\n"~
90 		         "Actual: %s",
91 		         file, line, msg=="" ? "" : ": " ~ msg,
92 		         valueStr, condStr, valueResult);
93 		writeln();
94 	}
95 	
96 	return condResult;
97 }
98 
99 void _deferEnsureException(long line, string file, string valueStr, string condStr, string msg="")(Object thrown)
100 {
101 	assertCount++;
102 	writef("%s(%s): Ensure Threw%s:\n"~
103 	       "Expression '%s':\n"~
104 	       "Expected: %s\n"~
105 	       "Threw: ",
106 	       file, line, msg=="" ? "" : ": " ~ msg,
107 	       valueStr, condStr);
108 	Exception e = cast(Exception)thrown;
109 	if(e)
110 		writeln(thrown);
111 	else
112 		writefln("Object: type '%s': %s", thrown.classinfo.name, thrown);
113 	writeln();
114 }
115 
116 template deferEnsureThrows(string stmtStr, TExpected, string msg="")
117 {
118 	enum deferEnsureThrows =
119 	// The "_deferAssert_line" is a workaround for DMD Bug #2887
120 	"{ enum long _deferAssert_line = __LINE__;\n"~
121 	"    Object _deferAssert_caught=null;\n"~
122 	"    try\n"~
123 	"    {"~stmtStr~"}\n"~
124 	"    catch(Throwable _deferAssert_e)\n"~
125 	"        _deferAssert_caught = _deferAssert_e;\n"~
126 	"    _deferEnsureThrows!(_deferAssert_line, __FILE__, "~stmtStr.stringof~", "~TExpected.stringof~", "~msg.stringof~")(_deferAssert_caught);\n"~
127 	"}\n";
128 }
129 
130 void _deferEnsureThrows(long line, string file, string stmtStr, TExpected, string msg="")(Object thrown)
131 {
132 	string actualType = (thrown is null)? "{null}" : thrown.classinfo.name;
133 	
134 	if(actualType != TExpected.classinfo.name)
135 	{
136 		assertCount++;
137 		writef("%s(%s): Ensure Throw Failed%s\n"~
138 		       "Statement '%s':\n"~
139 		       "Expected: %s\n"~
140 		       "Actual:   ",
141 		       file, line, msg=="" ? "" : ": " ~ msg,
142 		       stmtStr, TExpected.classinfo.name, actualType);
143 		Throwable e = cast(Exception)thrown;
144 		if(e)
145 			writeln(e); //e.writeOut( (string msg) {Stdout(msg);} );
146 		else
147 			writefln("%s: %s", actualType, thrown);
148 		writeln();
149 	}
150 }
151 
152 private uint assertCount=0;
153 uint getAssertCount()
154 {
155 	return assertCount;
156 }
157 void resetAssertCount()
158 {
159 	assertCount = 0;
160 }
161 
162 void flushAsserts()
163 {
164 	if(assertCount > 0)
165 	{
166 		uint saveAssertCount = assertCount;
167 		assertCount = 0;
168 		stdout.flush();
169 		assert(false,
170 			to!(string)(saveAssertCount) ~
171 			" Assert Failure" ~
172 			(saveAssertCount == 1 ? "" : "s")
173 		);
174 	}
175 }
176 
177 /++
178 To be mixed in.
179 
180 Note that if DMD Issue #2887 ever gets fixed, the line numbers for errors
181 in unittestBody may get messed up.
182 
183 Suggested Usage:
184 -------------------
185 alias unittestSection!"MyProject_unittest" unittestMyProject;
186 
187 mixin(unittestMyProject(q{
188 	// put unittests here
189 }));
190 
191 mixin(unittestMyProject("This is for class Foo", q{
192 	// put unittests here
193 }));
194 -------------------
195 
196 That will create a named unittest section that will only run
197 when -unittest and -debug=MyProject_unittest are passed to DMD.
198 When run, the following headings will be displayed:
199 
200 == unittest: the.module.name
201 == unittest: the.module.name: This is for class Foo
202 +/
203 string unittestSection(string debugIdent, bool autoThrow=false)(string sectionName, string unittestBody=null)
204 {
205 	// Allow these two forms (without getting in the way of aliasing):
206 	//   unittestSection!debugIdent(unittestBody)
207 	//   unittestSection!debugIdent(sectionName, unittestBody)
208 	if(unittestBody==null)
209 	{
210 		unittestBody = sectionName;
211 		sectionName = "";
212 	}
213 	sectionName = escapeDDQS!string( ( sectionName==""? "" : ": "~sectionName ) );
214 	auto autoThrowStr = autoThrow? "true" : "false";
215 	
216 	return
217 		"debug("~debugIdent~") "~
218 		"{ "~
219 		"	unittest "~
220 		"	{ "~
221 		"		auto saveAutoThrow = semitwist.util.unittests.autoThrow; "~
222 		"		semitwist.util.unittests.autoThrow = "~autoThrowStr~"; "~
223 		"		scope(exit) semitwist.util.unittests.autoThrow = saveAutoThrow; "~
224 		"		 "~
225 		"		int _unittestSection_dummy_; "~
226 		"		auto _unittestSection_moduleName_ = "~
227 		"			unittestSection_demangle( qualifiedName!_unittestSection_dummy_() ) "~
228 		"				[ "~
229 		"					\"void \".length .. "~
230 		"					ctfe_find(unittestSection_demangle( qualifiedName!_unittestSection_dummy_() ), \".__unittest\") "~
231 		"				]; "~
232 		" "~
233 		"		writeUnittestSection( "~
234 		"			_unittestSection_moduleName_ ~ "~
235 		"			"~sectionName~" "~
236 		"		); "~
237 		"		"~unittestBody~" "~
238 		"	} "~
239 		"} ";
240 }
241 alias mangledName unittestSection_mangledName;
242 alias demangle unittestSection_demangle;
243 
244 void writeUnittestSection(string sectionName)
245 {
246 	writeln("== unittest: ", sectionName);
247 }
248 
249 alias unittestSection!"SemiTwistDLib_unittest" unittestSemiTwistDLib;
250 
251 ///////////////////////////////////////////////////////////////////////////////
252 
253 /// A modification of Jonathan M Davis's unittest routines below:
254 
255 //Ideally, this would be safe, I suppose, but it's enough of
256 //a pain at the moment to make stuff safe that I'm just going to
257 //mark it as trusted for the moment.
258 @trusted
259 
260 
261 import std.stdio;
262 
263 import core.exception;
264 
265 import std.algorithm;
266 import std.array;
267 import std.conv;
268 import std.exception;
269 import std.functional;
270 import std.range;
271 import std..string;
272 import std.traits;
273 
274 /// This does not currently affect the defer* functions above.
275 bool autoThrow = true;
276 
277 private void throwException(Throwable e)
278 {
279 	if(autoThrow)
280 		throw e;
281 	else
282 	{
283 		assertCount++;
284 		writeln(e);
285 		writeln();
286 	}
287 }
288 
289 version(unittest)
290 {
291     import std.datetime;
292 }
293 
294 
295 mixin(unittestSemiTwistDLib("assertPred: Overview Examples", q{
296 	autoThrow = true;
297 
298     //Verify Examples.
299     assertPred!"=="(5 * 7, 35);
300 
301     assertPred!("opCmp", ">")(std.datetime.Clock.currTime(), std.datetime.SysTime(Date(1970, 1, 1)));
302 
303     assertPred!"opAssign"(std.datetime.SysTime(Date(1970, 1, 1)),
304                           std.datetime.SysTime(Date(2010, 12, 31)));
305 
306     assertPred!"+"(5, 7, 12);
307 
308     assertPred!"+="(std.datetime.SysTime(Date(1970, 1, 1)),
309                     core.time.dur!"days"(3),
310                     std.datetime.SysTime(Date(1970, 1, 4)));
311 
312     assertPred!"a == 7"(12 - 5);
313 
314     assertPred!"a == b + 5"(12, 7);
315 
316     assertPred!((int a, int b, int c){return a + b < c;})(4, 12, 50);
317 }));
318 
319 
320 void assertPred(string op, L, R)
321                (L lhs, R rhs, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
322     if((op == "<" ||
323         op == "<=" ||
324         op == "==" ||
325         op == "!=" ||
326         op == ">=" ||
327         op == ">") &&
328        __traits(compiles, mixin("lhs " ~ op ~ " rhs")) &&
329        isPrintable!L &&
330        isPrintable!R)
331 {
332     immutable result = mixin("lhs " ~ op ~ " rhs");
333 
334     if(!result)
335     {
336         immutable tail = msg.empty ? "." : ": " ~ msg;
337 
338         throwException( new AssertError(format("assertPred!\"%s\" failed:\n[%s] (lhs)\n[%s] (rhs)%s", op, lhs, rhs, tail),
339                                         file,
340                                         line)
341 		);
342     }
343 }
344 
345 mixin(unittestSemiTwistDLib("assertPred: Comparison Operators", q{
346 	autoThrow = true;
347 
348     struct IntWrapper
349     {
350         int value;
351 
352         this(int value)
353         {
354             this.value = value;
355         }
356 
357         string toString() const
358         {
359             return to!string(value);
360         }
361     }
362 
363     //Test ==.
364     assertNotThrown!AssertError(assertPred!"=="(6, 6));
365     assertNotThrown!AssertError(assertPred!"=="(6, 6.0));
366     assertNotThrown!AssertError(assertPred!"=="(IntWrapper(6), IntWrapper(6)));
367 
368     assertThrown!AssertError(assertPred!"=="(6, 7));
369     assertThrown!AssertError(assertPred!"=="(6, 6.1));
370     assertThrown!AssertError(assertPred!"=="(IntWrapper(6), IntWrapper(7)));
371     assertThrown!AssertError(assertPred!"=="(IntWrapper(7), IntWrapper(6)));
372 
373     assertPred!"=="(collectExceptionMsg(assertPred!"=="(6, 7)),
374                     "assertPred!\"==\" failed:\n[6] (lhs)\n[7] (rhs).");
375     assertPred!"=="(collectExceptionMsg(assertPred!"=="(6, 7, "It failed!")),
376                     "assertPred!\"==\" failed:\n[6] (lhs)\n[7] (rhs): It failed!");
377 
378     //Test !=.
379     assertNotThrown!AssertError(assertPred!"!="(6, 7));
380     assertNotThrown!AssertError(assertPred!"!="(6, 6.1));
381     assertNotThrown!AssertError(assertPred!"!="(IntWrapper(6), IntWrapper(7)));
382     assertNotThrown!AssertError(assertPred!"!="(IntWrapper(7), IntWrapper(6)));
383 
384     assertThrown!AssertError(assertPred!"!="(6, 6));
385     assertThrown!AssertError(assertPred!"!="(6, 6.0));
386     assertThrown!AssertError(assertPred!"!="(IntWrapper(6), IntWrapper(6)));
387 
388     assertPred!"=="(collectExceptionMsg(assertPred!"!="(6, 6)),
389                     "assertPred!\"!=\" failed:\n[6] (lhs)\n[6] (rhs).");
390     assertPred!"=="(collectExceptionMsg(assertPred!"!="(6, 6, "It failed!")),
391                     "assertPred!\"!=\" failed:\n[6] (lhs)\n[6] (rhs): It failed!");
392 
393     //Test <, <=, >=, >.
394     assertNotThrown!AssertError(assertPred!"<"(5, 7));
395     assertNotThrown!AssertError(assertPred!"<="(5, 7));
396     assertNotThrown!AssertError(assertPred!"<="(5, 5));
397     assertNotThrown!AssertError(assertPred!">="(7, 7));
398     assertNotThrown!AssertError(assertPred!">="(7, 5));
399     assertNotThrown!AssertError(assertPred!">"(7, 5));
400 
401     assertThrown!AssertError(assertPred!"<"(7, 5));
402     assertThrown!AssertError(assertPred!"<="(7, 5));
403     assertThrown!AssertError(assertPred!">="(5, 7));
404     assertThrown!AssertError(assertPred!">"(5, 7));
405 
406     assertPred!"=="(collectExceptionMsg(assertPred!"<"(7, 5)),
407                     "assertPred!\"<\" failed:\n[7] (lhs)\n[5] (rhs).");
408     assertPred!"=="(collectExceptionMsg(assertPred!"<"(7, 5, "It failed!")),
409                     "assertPred!\"<\" failed:\n[7] (lhs)\n[5] (rhs): It failed!");
410 
411     //Test default arguments.
412     assertPred!"=="(12, 12);
413     assertPred!"=="(12, 12, "msg");
414     assertPred!"=="(12, 12, "msg", "file");
415     assertPred!"=="(12, 12, "msg", "file", 42);
416 
417     //Verify Examples.
418     assertPred!"<"(5 / 2 + 4, 27);
419 
420     assertPred!"<="(4, 5);
421 
422     assertPred!"=="(1 * 2.1, 2.1);
423 
424     assertPred!"!="("hello " ~ "world", "goodbye world");
425 
426     assertPred!">="(14.2, 14);
427 
428     assertPred!">"(15, 2 + 1);
429 
430     assert(collectExceptionMsg(assertPred!"=="("hello", "goodbye")) ==
431            "assertPred!\"==\" failed:\n" ~
432            "[hello] (lhs)\n" ~
433            "[goodbye] (rhs).");
434 
435     assert(collectExceptionMsg(assertPred!"<"(5, 2, "My test failed!")) ==
436            "assertPred!\"<\" failed:\n" ~
437            "[5] (lhs)\n" ~
438            "[2] (rhs): My test failed!");
439 }));
440 
441 
442 void assertPred(string func, string expected, L, R)
443                (L lhs, R rhs, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
444     if(func == "opCmp" &&
445        (expected == "<" ||
446         expected == "==" ||
447         expected == ">") &&
448        __traits(compiles, lhs.opCmp(rhs)) &&
449        isPrintable!L &&
450        isPrintable!R)
451 {
452     immutable result = lhs.opCmp(rhs);
453 
454     if(mixin("result " ~ expected ~ " 0"))
455         return;
456 
457     immutable tail = msg.empty ? "." : ": " ~ msg;
458     immutable actual = result < 0 ? "<" : (result == 0 ? "==" : ">");
459 
460     throwException( new AssertError(format("assertPred!(\"opCmp\", \"%s\") failed:\n[%s] %s\n[%s]%s", expected, lhs, actual, rhs, tail),
461                                     file,
462                                     line)
463 	);
464 }
465 
466 mixin(unittestSemiTwistDLib("assertPred: opCmp", q{
467 	autoThrow = true;
468 
469     struct IntWrapper
470     {
471         int value;
472 
473         this(int value)
474         {
475             this.value = value;
476         }
477 
478         int opCmp(const ref IntWrapper rhs) const
479         {
480             if(value < rhs.value)
481                 return -1;
482             else if(value > rhs.value)
483                 return 1;
484 
485             return 0;
486         }
487 
488         string toString() const
489         {
490             return to!string(value);
491         }
492     }
493 
494     assertNotThrown!AssertError(assertPred!("opCmp", "<")(IntWrapper(0), IntWrapper(6)));
495     assertNotThrown!AssertError(assertPred!("opCmp", "<")(IntWrapper(6), IntWrapper(7)));
496     assertNotThrown!AssertError(assertPred!("opCmp", "==")(IntWrapper(6), IntWrapper(6)));
497     assertNotThrown!AssertError(assertPred!("opCmp", "==")(IntWrapper(0), IntWrapper(0)));
498     assertNotThrown!AssertError(assertPred!("opCmp", ">")(IntWrapper(6), IntWrapper(0)));
499     assertNotThrown!AssertError(assertPred!("opCmp", ">")(IntWrapper(7), IntWrapper(6)));
500 
501     assertThrown!AssertError(assertPred!("opCmp", "<")(IntWrapper(6), IntWrapper(6)));
502     assertThrown!AssertError(assertPred!("opCmp", "<")(IntWrapper(7), IntWrapper(6)));
503     assertThrown!AssertError(assertPred!("opCmp", "==")(IntWrapper(6), IntWrapper(7)));
504     assertThrown!AssertError(assertPred!("opCmp", "==")(IntWrapper(7), IntWrapper(6)));
505     assertThrown!AssertError(assertPred!("opCmp", ">")(IntWrapper(6), IntWrapper(6)));
506     assertThrown!AssertError(assertPred!("opCmp", ">")(IntWrapper(6), IntWrapper(7)));
507 
508     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "<")(IntWrapper(5), IntWrapper(5))),
509                     "assertPred!(\"opCmp\", \"<\") failed:\n[5] ==\n[5].");
510     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "<")(IntWrapper(5), IntWrapper(5), "It failed!")),
511                     "assertPred!(\"opCmp\", \"<\") failed:\n[5] ==\n[5]: It failed!");
512 
513     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "<")(IntWrapper(14), IntWrapper(7))),
514                     "assertPred!(\"opCmp\", \"<\") failed:\n[14] >\n[7].");
515     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "<")(IntWrapper(14), IntWrapper(7), "It failed!")),
516                     "assertPred!(\"opCmp\", \"<\") failed:\n[14] >\n[7]: It failed!");
517 
518     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "==")(IntWrapper(5), IntWrapper(7))),
519                     "assertPred!(\"opCmp\", \"==\") failed:\n[5] <\n[7].");
520     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "==")(IntWrapper(5), IntWrapper(7), "It failed!")),
521                     "assertPred!(\"opCmp\", \"==\") failed:\n[5] <\n[7]: It failed!");
522 
523     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "==")(IntWrapper(14), IntWrapper(7))),
524                     "assertPred!(\"opCmp\", \"==\") failed:\n[14] >\n[7].");
525     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", "==")(IntWrapper(14), IntWrapper(7), "It failed!")),
526                     "assertPred!(\"opCmp\", \"==\") failed:\n[14] >\n[7]: It failed!");
527 
528     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", ">")(IntWrapper(5), IntWrapper(7))),
529                     "assertPred!(\"opCmp\", \">\") failed:\n[5] <\n[7].");
530     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", ">")(IntWrapper(5), IntWrapper(7), "It failed!")),
531                     "assertPred!(\"opCmp\", \">\") failed:\n[5] <\n[7]: It failed!");
532 
533     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", ">")(IntWrapper(7), IntWrapper(7))),
534                     "assertPred!(\"opCmp\", \">\") failed:\n[7] ==\n[7].");
535     assertPred!"=="(collectExceptionMsg(assertPred!("opCmp", ">")(IntWrapper(7), IntWrapper(7), "It failed!")),
536                     "assertPred!(\"opCmp\", \">\") failed:\n[7] ==\n[7]: It failed!");
537 
538     //Test default arguments.
539     assertPred!("opCmp", "<")(Date(2010, 11, 30), Date(2010, 12, 31));
540     assertPred!("opCmp", "<")(Date(2010, 11, 30), Date(2010, 12, 31), "msg");
541     assertPred!("opCmp", "<")(Date(2010, 11, 30), Date(2010, 12, 31), "msg", "file");
542     assertPred!("opCmp", "<")(Date(2010, 11, 30), Date(2010, 12, 31), "msg", "file", 42);
543 
544     assertPred!("opCmp", "==")(Date(2010, 12, 31), Date(2010, 12, 31));
545     assertPred!("opCmp", "==")(Date(2010, 12, 31), Date(2010, 12, 31), "msg");
546     assertPred!("opCmp", "==")(Date(2010, 12, 31), Date(2010, 12, 31), "msg", "file");
547     assertPred!("opCmp", "==")(Date(2010, 12, 31), Date(2010, 12, 31), "msg", "file", 42);
548 
549     assertPred!("opCmp", ">")(Date(2010, 12, 31), Date(2010, 11, 30));
550     assertPred!("opCmp", ">")(Date(2010, 12, 31), Date(2010, 11, 30), "msg");
551     assertPred!("opCmp", ">")(Date(2010, 12, 31), Date(2010, 11, 30), "msg", "file");
552     assertPred!("opCmp", ">")(Date(2010, 12, 31), Date(2010, 11, 30), "msg", "file", 42);
553 }));
554 
555 mixin(unittestSemiTwistDLib("assertPred: opCmp: Examples", q{
556 	autoThrow = true;
557 
558     //Verify Examples
559     assertPred!("opCmp", "<")(std.datetime.SysTime(Date(1970, 1, 1)),
560                               std.datetime.SysTime(Date(2010, 12, 31)));
561 
562     assertPred!("opCmp", "==")(std.datetime.SysTime(Date(1970, 1, 1)),
563                                std.datetime.SysTime(Date(1970, 1, 1)));
564 
565     assertPred!("opCmp", ">")(std.datetime.SysTime(Date(2010, 12, 31)),
566                               std.datetime.SysTime(Date(1970, 1, 1)));
567 
568     assert(collectExceptionMsg(assertPred!("opCmp", "<")(std.datetime.SysTime(Date(2010, 12, 31)),
569                                                          std.datetime.SysTime(Date(1970, 1, 1)))) ==
570            "assertPred!(\"opCmp\", \"<\") failed:\n" ~
571            "[2010-Dec-31 00:00:00] >\n" ~
572            "[1970-Jan-01 00:00:00].");
573 
574     assert(collectExceptionMsg(assertPred!("opCmp", "==")(std.datetime.SysTime(Date(1970, 1, 1)),
575                                                           std.datetime.SysTime(Date(2010, 12, 31)))) ==
576            "assertPred!(\"opCmp\", \"==\") failed:\n" ~
577            "[1970-Jan-01 00:00:00] <\n" ~
578            "[2010-Dec-31 00:00:00].");
579 
580     assert(collectExceptionMsg(assertPred!("opCmp", ">")(std.datetime.SysTime(Date(1970, 1, 1)),
581                                                          std.datetime.SysTime(Date(1970, 1, 1)))) ==
582            "assertPred!(\"opCmp\", \">\") failed:\n" ~
583            "[1970-Jan-01 00:00:00] ==\n" ~
584            "[1970-Jan-01 00:00:00].");
585 }));
586 
587 
588 void assertPred(string func, L, R)
589                (L lhs, R rhs, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
590     if(func == "opAssign" &&
591        __traits(compiles, lhs = rhs) &&
592        __traits(compiles, lhs == rhs) &&
593        __traits(compiles, (lhs = rhs) == rhs) &&
594        isPrintable!L &&
595        isPrintable!R)
596 {
597     auto result = lhs = rhs;
598 
599     if(lhs != rhs)
600     {
601         immutable tail = msg.empty ? "." : ": " ~ msg;
602 
603         throwException( new AssertError(format("assertPred!\"opAssign\" failed: lhs was assigned to\n[%s] instead of\n[%s]%s",
604                                                lhs,
605                                                rhs,
606                                                tail),
607                                         file,
608                                         line)
609 		);
610     }
611 
612     if(result != rhs)
613     {
614         immutable tail = msg.empty ? "." : ": " ~ msg;
615 
616         throwException( new AssertError(format("assertPred!\"opAssign\" failed:\n[%s] (return value) !=\n[%s] (assigned value)%s",
617                                                result,
618                                                rhs,
619                                                tail),
620                                         file,
621                                         line)
622 		);
623     }
624 }
625 
626 mixin(unittestSemiTwistDLib("assertPred: opAssign", q{
627 	autoThrow = true;
628 
629     struct IntWrapper
630     {
631         int value;
632 
633         this(int value)
634         {
635             this.value = value;
636         }
637 
638         IntWrapper opAssign(IntWrapper rhs)
639         {
640             this.value = rhs.value;
641 
642             return this;
643         }
644 
645         string toString() const
646         {
647             return to!string(value);
648         }
649     }
650 
651     struct IntWrapper_BadAssign
652     {
653         int value;
654 
655         this(int value)
656         {
657             this.value = value;
658         }
659 
660         IntWrapper_BadAssign opAssign(IntWrapper_BadAssign rhs)
661         {
662             this.value = -rhs.value;
663 
664             return IntWrapper_BadAssign(rhs.value);
665         }
666 
667         string toString() const
668         {
669             return to!string(value);
670         }
671     }
672 
673     struct IntWrapper_BadReturn
674     {
675         int value;
676 
677         this(int value)
678         {
679             this.value = value;
680         }
681 
682         IntWrapper_BadReturn opAssign(IntWrapper_BadReturn rhs)
683         {
684             this.value = rhs.value;
685 
686             return IntWrapper_BadReturn(-rhs.value);
687         }
688 
689         string toString() const
690         {
691             return to!string(value);
692         }
693     }
694 
695     assertNotThrown!AssertError(assertPred!"opAssign"(IntWrapper(5), IntWrapper(2)));
696 
697     assertThrown!AssertError(assertPred!"opAssign"(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2)));
698     assertThrown!AssertError(assertPred!"opAssign"(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2)));
699 
700     assertPred!"=="(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2))),
701                     "assertPred!\"opAssign\" failed: lhs was assigned to\n[-2] instead of\n[2].");
702     assertPred!"=="(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadAssign(5),
703                                                               IntWrapper_BadAssign(2),
704                                                               "It failed!")),
705                     "assertPred!\"opAssign\" failed: lhs was assigned to\n[-2] instead of\n[2]: It failed!");
706 
707     assertPred!"=="(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2))),
708                     "assertPred!\"opAssign\" failed:\n[-2] (return value) !=\n[2] (assigned value).");
709     assertPred!"=="(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadReturn(5),
710                                                               IntWrapper_BadReturn(2),
711                                                               "It failed!")),
712                     "assertPred!\"opAssign\" failed:\n[-2] (return value) !=\n[2] (assigned value): It failed!");
713 
714     //Test default arguments.
715     assertPred!"opAssign"(0, 12);
716     assertPred!"opAssign"(0, 12, "msg");
717     assertPred!"opAssign"(0, 12, "msg", "file");
718     assertPred!"opAssign"(0, 12, "msg", "file", 42);
719 }));
720 
721 mixin(unittestSemiTwistDLib("assertPred: opAssign: Examples", q{
722 	autoThrow = true;
723 
724     //Verify Examples
725     assertPred!"opAssign"(std.datetime.SysTime(Date(1970, 1, 1)), std.datetime.SysTime(Date(2000, 12, 12)));
726 
727     struct IntWrapper_BadAssign
728     {
729         int value;
730 
731         IntWrapper_BadAssign opAssign(IntWrapper_BadAssign rhs)
732         {
733             this.value = -rhs.value;
734 
735             return IntWrapper_BadAssign(rhs.value);
736         }
737 
738         string toString() const { return to!string(value); }
739     }
740 
741     assert(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2))) ==
742            "assertPred!\"opAssign\" failed: lhs was assigned to\n" ~
743            "[-2] instead of\n" ~
744            "[2].");
745 
746     assert(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadAssign(5),
747                                                      IntWrapper_BadAssign(2),
748                                                      "It failed!")) ==
749            "assertPred!\"opAssign\" failed: lhs was assigned to\n" ~
750            "[-2] instead of\n" ~
751            "[2]: It failed!");
752 
753 
754     struct IntWrapper_BadReturn
755     {
756         int value;
757 
758         IntWrapper_BadReturn opAssign(IntWrapper_BadReturn rhs)
759         {
760             this.value = rhs.value;
761 
762             return IntWrapper_BadReturn(-rhs.value);
763         }
764 
765         string toString() const { return to!string(value); }
766     }
767 
768     assert(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2))) ==
769            "assertPred!\"opAssign\" failed:\n" ~
770            "[-2] (return value) !=\n" ~
771            "[2] (assigned value).");
772 
773     assert(collectExceptionMsg(assertPred!"opAssign"(IntWrapper_BadReturn(5),
774                                                      IntWrapper_BadReturn(2),
775                                                      "It failed!")) ==
776            "assertPred!\"opAssign\" failed:\n" ~
777            "[-2] (return value) !=\n" ~
778            "[2] (assigned value): It failed!");
779 }));
780 
781 
782 void assertPred(string op, L, R, E)
783                (L lhs, R rhs, E expected, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
784     if((op == "+" ||
785         op == "-" ||
786         op == "*" ||
787         op == "/" ||
788         op == "%" ||
789         op == "^^" ||
790         op == "&" ||
791         op == "|" ||
792         op == "^" ||
793         op == "<<" ||
794         op == ">>" ||
795         op == ">>>" ||
796         op == "~") &&
797        __traits(compiles, mixin("lhs " ~ op ~ " rhs")) &&
798        __traits(compiles, mixin("(lhs " ~ op ~ " rhs) == expected")) &&
799        isPrintable!L &&
800        isPrintable!R)
801 {
802     const result = mixin("lhs " ~ op ~ " rhs");
803 
804     if(result != expected)
805     {
806         immutable tail = msg.empty ? "." : ": " ~ msg;
807 
808         throwException( new AssertError(format("assertPred!\"%s\" failed: [%s] %s [%s]:\n[%s] (actual)\n[%s] (expected)%s",
809                                                op,
810                                                lhs,
811                                                op,
812                                                rhs,
813                                                result,
814                                                expected,
815                                                tail),
816                                          file,
817                                          line)
818 		);
819     }
820 }
821 
822 mixin(unittestSemiTwistDLib("assertPred: Operators", q{
823 	autoThrow = true;
824 
825     assertNotThrown!AssertError(assertPred!"+"(7, 5, 12));
826     assertNotThrown!AssertError(assertPred!"-"(7, 5, 2));
827     assertNotThrown!AssertError(assertPred!"*"(7, 5, 35));
828     assertNotThrown!AssertError(assertPred!"/"(7, 5, 1));
829     assertNotThrown!AssertError(assertPred!"%"(7, 5, 2));
830     assertNotThrown!AssertError(assertPred!"^^"(7, 5, 16_807));
831     assertNotThrown!AssertError(assertPred!"&"(7, 5, 5));
832     assertNotThrown!AssertError(assertPred!"|"(7, 5, 7));
833     assertNotThrown!AssertError(assertPred!"^"(7, 5, 2));
834     assertNotThrown!AssertError(assertPred!"<<"(7, 1, 14));
835     assertNotThrown!AssertError(assertPred!">>"(7, 1, 3));
836     assertNotThrown!AssertError(assertPred!">>>"(-7, 1, 2_147_483_644));
837     assertNotThrown!AssertError(assertPred!"~"("hello ", "world", "hello world"));
838 
839     assertThrown!AssertError(assertPred!"+"(7, 5, 0));
840     assertThrown!AssertError(assertPred!"-"(7, 5, 0));
841     assertThrown!AssertError(assertPred!"*"(7, 5, 0));
842     assertThrown!AssertError(assertPred!"/"(7, 5, 0));
843     assertThrown!AssertError(assertPred!"%"(7, 5, 0));
844     assertThrown!AssertError(assertPred!"^^"(7, 5, 0));
845     assertThrown!AssertError(assertPred!"&"(7, 5, 0));
846     assertThrown!AssertError(assertPred!"|"(7, 5, 0));
847     assertThrown!AssertError(assertPred!"^"(7, 5, 0));
848     assertThrown!AssertError(assertPred!"<<"(7, 1, 0));
849     assertThrown!AssertError(assertPred!">>"(7, 1, 0));
850     assertThrown!AssertError(assertPred!">>>"(-7, 1, 0));
851     assertThrown!AssertError(assertPred!"~"("hello ", "world", "goodbye world"));
852 
853     assertPred!"=="(collectExceptionMsg(assertPred!"+"(7, 5, 11)),
854                     "assertPred!\"+\" failed: [7] + [5]:\n[12] (actual)\n[11] (expected).");
855     assertPred!"=="(collectExceptionMsg(assertPred!"+"(7, 5, 11, "It failed!")),
856                     "assertPred!\"+\" failed: [7] + [5]:\n[12] (actual)\n[11] (expected): It failed!");
857 
858     assertPred!"=="(collectExceptionMsg(assertPred!"^^"(7, 5, 42)),
859                     "assertPred!\"^^\" failed: [7] ^^ [5]:\n[16807] (actual)\n[42] (expected).");
860     assertPred!"=="(collectExceptionMsg(assertPred!"^^"(7, 5, 42, "It failed!")),
861                     "assertPred!\"^^\" failed: [7] ^^ [5]:\n[16807] (actual)\n[42] (expected): It failed!");
862 
863     assertPred!"=="(collectExceptionMsg(assertPred!"~"("hello ", "world", "goodbye world")),
864                     "assertPred!\"~\" failed: [hello ] ~ [world]:\n[hello world] (actual)\n[goodbye world] (expected).");
865     assertPred!"=="(collectExceptionMsg(assertPred!"~"("hello ", "world", "goodbye world", "It failed!")),
866                     "assertPred!\"~\" failed: [hello ] ~ [world]:\n[hello world] (actual)\n[goodbye world] (expected): It failed!");
867 
868     //Verify Examples
869     assertPred!"+"(7, 5, 12);
870     assertPred!"-"(7, 5, 2);
871     assertPred!"*"(7, 5, 35);
872     assertPred!"/"(7, 5, 1);
873     assertPred!"%"(7, 5, 2);
874     assertPred!"^^"(7, 5, 16_807);
875     assertPred!"&"(7, 5, 5);
876     assertPred!"|"(7, 5, 7);
877     assertPred!"^"(7, 5, 2);
878     assertPred!"<<"(7, 1, 14);
879     assertPred!">>"(7, 1, 3);
880     assertPred!">>>"(-7, 1, 2_147_483_644);
881     assertPred!"~"("hello ", "world", "hello world");
882 
883     assert(collectExceptionMsg(assertPred!"+"(7, 5, 11)) ==
884            "assertPred!\"+\" failed: [7] + [5]:\n" ~
885            "[12] (actual)\n" ~
886            "[11] (expected).");
887 
888     assert(collectExceptionMsg(assertPred!"/"(11, 2, 6, "It failed!")) ==
889            "assertPred!\"/\" failed: [11] / [2]:\n" ~
890            "[5] (actual)\n" ~
891            "[6] (expected): It failed!");
892 
893     //Test default arguments.
894     assertPred!"+"(0, 12, 12);
895     assertPred!"+"(0, 12, 12, "msg");
896     assertPred!"+"(0, 12, 12, "msg", "file");
897     assertPred!"+"(0, 12, 12, "msg", "file", 42);
898 }));
899 
900 
901 void assertPred(string op, L, R, E)
902                (L lhs, R rhs, E expected, lazy string msg = null, string file = __FILE__, size_t line = __LINE__)
903     if((op == "+=" ||
904         op == "-=" ||
905         op == "*=" ||
906         op == "/=" ||
907         op == "%=" ||
908         op == "^^=" ||
909         op == "&=" ||
910         op == "|=" ||
911         op == "^=" ||
912         op == "<<=" ||
913         op == ">>=" ||
914         op == ">>>=" ||
915         op == "~=") &&
916        __traits(compiles, mixin("lhs " ~ op ~ " rhs")) &&
917        __traits(compiles, mixin("(lhs " ~ op ~ " rhs) == expected")) &&
918        isPrintable!L &&
919        isPrintable!R)
920 {
921     immutable origLHSStr = to!string(lhs);
922     const result = mixin("lhs " ~ op ~ " rhs");
923 
924     if(lhs != expected)
925     {
926         immutable tail = msg.empty ? "." : ": " ~ msg;
927 
928         throwException( new AssertError(format("assertPred!\"%s\" failed: After [%s] %s [%s], lhs was assigned to\n[%s] instead of\n[%s]%s",
929                                                op,
930                                                origLHSStr,
931                                                op,
932                                                rhs,
933                                                lhs,
934                                                expected,
935                                                tail),
936                                          file,
937                                          line)
938 		);
939     }
940 
941     if(result != expected)
942     {
943         immutable tail = msg.empty ? "." : ": " ~ msg;
944 
945         throwException( new AssertError(format("assertPred!\"%s\" failed: Return value of [%s] %s [%s] was\n[%s] instead of\n[%s]%s",
946                                                op,
947                                                origLHSStr,
948                                                op,
949                                                rhs,
950                                                result,
951                                                expected,
952                                                tail),
953                                          file,
954                                          line)
955 		);
956     }
957 }
958 
959 mixin(unittestSemiTwistDLib("assertPred: Assignment Operators", q{
960 	autoThrow = true;
961 
962     assertNotThrown!AssertError(assertPred!"+="(7, 5, 12));
963     assertNotThrown!AssertError(assertPred!"-="(7, 5, 2));
964     assertNotThrown!AssertError(assertPred!"*="(7, 5, 35));
965     assertNotThrown!AssertError(assertPred!"/="(7, 5, 1));
966     assertNotThrown!AssertError(assertPred!"%="(7, 5, 2));
967     assertNotThrown!AssertError(assertPred!"^^="(7, 5, 16_807));
968     assertNotThrown!AssertError(assertPred!"&="(7, 5, 5));
969     assertNotThrown!AssertError(assertPred!"|="(7, 5, 7));
970     assertNotThrown!AssertError(assertPred!"^="(7, 5, 2));
971     assertNotThrown!AssertError(assertPred!"<<="(7, 1, 14));
972     assertNotThrown!AssertError(assertPred!">>="(7, 1, 3));
973     assertNotThrown!AssertError(assertPred!">>>="(-7, 1, 2_147_483_644));
974     assertNotThrown!AssertError(assertPred!"~="("hello ", "world", "hello world"));
975 
976     assertThrown!AssertError(assertPred!"+="(7, 5, 0));
977     assertThrown!AssertError(assertPred!"-="(7, 5, 0));
978     assertThrown!AssertError(assertPred!"*="(7, 5, 0));
979     assertThrown!AssertError(assertPred!"/="(7, 5, 0));
980     assertThrown!AssertError(assertPred!"%="(7, 5, 0));
981     assertThrown!AssertError(assertPred!"^^="(7, 5, 0));
982     assertThrown!AssertError(assertPred!"&="(7, 5, 0));
983     assertThrown!AssertError(assertPred!"|="(7, 5, 0));
984     assertThrown!AssertError(assertPred!"^="(7, 5, 0));
985     assertThrown!AssertError(assertPred!"<<="(7, 1, 0));
986     assertThrown!AssertError(assertPred!">>="(7, 1, 0));
987     assertThrown!AssertError(assertPred!">>>="(-7, 1, 0));
988     assertThrown!AssertError(assertPred!"~="("hello ", "world", "goodbye world"));
989 
990     assertPred!"=="(collectExceptionMsg(assertPred!"+="(7, 5, 11)),
991                     "assertPred!\"+=\" failed: After [7] += [5], lhs was assigned to\n[12] instead of\n[11].");
992     assertPred!"=="(collectExceptionMsg(assertPred!"+="(7, 5, 11, "It failed!")),
993                     "assertPred!\"+=\" failed: After [7] += [5], lhs was assigned to\n[12] instead of\n[11]: It failed!");
994 
995     assertPred!"=="(collectExceptionMsg(assertPred!"^^="(7, 5, 42)),
996                     "assertPred!\"^^=\" failed: After [7] ^^= [5], lhs was assigned to\n[16807] instead of\n[42].");
997     assertPred!"=="(collectExceptionMsg(assertPred!"^^="(7, 5, 42, "It failed!")),
998                     "assertPred!\"^^=\" failed: After [7] ^^= [5], lhs was assigned to\n[16807] instead of\n[42]: It failed!");
999 
1000     assertPred!"=="(collectExceptionMsg(assertPred!"~="("hello ", "world", "goodbye world")),
1001                     "assertPred!\"~=\" failed: After [hello ] ~= [world], lhs was assigned to\n[hello world] instead of\n[goodbye world].");
1002     assertPred!"=="(collectExceptionMsg(assertPred!"~="("hello ", "world", "goodbye world", "It failed!")),
1003                     "assertPred!\"~=\" failed: After [hello ] ~= [world], lhs was assigned to\n[hello world] instead of\n[goodbye world]: It failed!");
1004 
1005     struct IntWrapper
1006     {
1007         int value;
1008 
1009         this(int value)
1010         {
1011             this.value = value;
1012         }
1013 
1014         IntWrapper opOpAssign(string op)(IntWrapper rhs)
1015         {
1016             mixin("this.value " ~ op ~ "= rhs.value;");
1017 
1018             return this;
1019         }
1020 
1021         string toString() const
1022         {
1023             return to!string(value);
1024         }
1025     }
1026 
1027     struct IntWrapper_BadAssign
1028     {
1029         int value;
1030 
1031         this(int value)
1032         {
1033             this.value = value;
1034         }
1035 
1036         IntWrapper_BadAssign opOpAssign(string op)(IntWrapper_BadAssign rhs)
1037         {
1038             auto old = this.value;
1039 
1040             mixin("this.value " ~ op ~ "= -rhs.value;");
1041 
1042             return IntWrapper_BadAssign(mixin("old " ~ op ~ " rhs.value"));
1043         }
1044 
1045         string toString() const
1046         {
1047             return to!string(value);
1048         }
1049     }
1050 
1051     struct IntWrapper_BadReturn
1052     {
1053         int value;
1054 
1055         this(int value)
1056         {
1057             this.value = value;
1058         }
1059 
1060         IntWrapper_BadReturn opOpAssign(string op)(IntWrapper_BadReturn rhs)
1061         {
1062             mixin("this.value " ~ op ~ "= rhs.value;");
1063 
1064             return IntWrapper_BadReturn(rhs.value);
1065         }
1066 
1067         string toString() const
1068         {
1069             return to!string(value);
1070         }
1071     }
1072 
1073     assertNotThrown!AssertError(assertPred!"+="(IntWrapper(5), IntWrapper(2), IntWrapper(7)));
1074     assertNotThrown!AssertError(assertPred!"*="(IntWrapper(5), IntWrapper(2), IntWrapper(10)));
1075 
1076     assertThrown!AssertError(assertPred!"+="(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2), IntWrapper_BadAssign(7)));
1077     assertThrown!AssertError(assertPred!"+="(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2), IntWrapper_BadReturn(7)));
1078     assertThrown!AssertError(assertPred!"*="(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2), IntWrapper_BadAssign(10)));
1079     assertThrown!AssertError(assertPred!"*="(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2), IntWrapper_BadReturn(10)));
1080 
1081     assertPred!"=="(collectExceptionMsg(assertPred!"+="(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2), IntWrapper_BadAssign(7))),
1082                     "assertPred!\"+=\" failed: After [5] += [2], lhs was assigned to\n[3] instead of\n[7].");
1083     assertPred!"=="(collectExceptionMsg(assertPred!"+="(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2), IntWrapper_BadAssign(7), "It failed!")),
1084                     "assertPred!\"+=\" failed: After [5] += [2], lhs was assigned to\n[3] instead of\n[7]: It failed!");
1085 
1086     assertPred!"=="(collectExceptionMsg(assertPred!"+="(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2), IntWrapper_BadReturn(7))),
1087                     "assertPred!\"+=\" failed: Return value of [5] += [2] was\n[2] instead of\n[7].");
1088     assertPred!"=="(collectExceptionMsg(assertPred!"+="(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2), IntWrapper_BadReturn(7), "It failed!")),
1089                     "assertPred!\"+=\" failed: Return value of [5] += [2] was\n[2] instead of\n[7]: It failed!");
1090 
1091     assertPred!"=="(collectExceptionMsg(assertPred!"*="(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2), IntWrapper_BadAssign(10))),
1092                     "assertPred!\"*=\" failed: After [5] *= [2], lhs was assigned to\n[-10] instead of\n[10].");
1093     assertPred!"=="(collectExceptionMsg(assertPred!"*="(IntWrapper_BadAssign(5), IntWrapper_BadAssign(2), IntWrapper_BadAssign(10), "It failed!")),
1094                     "assertPred!\"*=\" failed: After [5] *= [2], lhs was assigned to\n[-10] instead of\n[10]: It failed!");
1095 
1096     assertPred!"=="(collectExceptionMsg(assertPred!"*="(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2), IntWrapper_BadReturn(10))),
1097                     "assertPred!\"*=\" failed: Return value of [5] *= [2] was\n[2] instead of\n[10].");
1098     assertPred!"=="(collectExceptionMsg(assertPred!"*="(IntWrapper_BadReturn(5), IntWrapper_BadReturn(2), IntWrapper_BadReturn(10), "It failed!")),
1099                     "assertPred!\"*=\" failed: Return value of [5] *= [2] was\n[2] instead of\n[10]: It failed!");
1100 
1101     //Test default arguments.
1102     assertPred!"+="(0, 12, 12);
1103     assertPred!"+="(0, 12, 12, "msg");
1104     assertPred!"+="(0, 12, 12, "msg", "file");
1105     assertPred!"+="(0, 12, 12, "msg", "file", 42);
1106 }));
1107 
1108 mixin(unittestSemiTwistDLib("assertPred: Assignment Operators: Examples", q{
1109 	autoThrow = true;
1110 
1111     //Verify Examples
1112     assertPred!"+="(5, 7, 12);
1113     assertPred!"-="(7, 5, 2);
1114     assertPred!"*="(7, 5, 35);
1115     assertPred!"/="(7, 5, 1);
1116     assertPred!"%="(7, 5, 2);
1117     assertPred!"^^="(7, 5, 16_807);
1118     assertPred!"&="(7, 5, 5);
1119     assertPred!"|="(7, 5, 7);
1120     assertPred!"^="(7, 5, 2);
1121     assertPred!"<<="(7, 1, 14);
1122     assertPred!">>="(7, 1, 3);
1123     assertPred!">>>="(-7, 1, 2_147_483_644);
1124     assertPred!"~="("hello ", "world", "hello world");
1125 
1126     struct IntWrapper_BadAssign
1127     {
1128         int value;
1129 
1130         IntWrapper_BadAssign opOpAssign(string op)(IntWrapper_BadAssign rhs)
1131         {
1132             auto old = this.value;
1133 
1134             mixin("this.value " ~ op ~ "= -rhs.value;");
1135 
1136             return IntWrapper_BadAssign(mixin("old " ~ op ~ " rhs.value"));
1137         }
1138 
1139         string toString() const { return to!string(value); }
1140     }
1141 
1142     assert(collectExceptionMsg(assertPred!"+="(IntWrapper_BadAssign(5),
1143                                                IntWrapper_BadAssign(2),
1144                                                IntWrapper_BadAssign(7))) ==
1145            "assertPred!\"+=\" failed: After [5] += [2], lhs was assigned to\n" ~
1146            "[3] instead of\n" ~
1147            "[7].");
1148 
1149     assert(collectExceptionMsg(assertPred!"+="(IntWrapper_BadAssign(5),
1150                                                IntWrapper_BadAssign(2),
1151                                                IntWrapper_BadAssign(7),
1152                                                "It failed!")) ==
1153            "assertPred!\"+=\" failed: After [5] += [2], lhs was assigned to\n" ~
1154            "[3] instead of\n" ~
1155            "[7]: It failed!");
1156 
1157     struct IntWrapper_BadReturn
1158     {
1159         int value;
1160 
1161         IntWrapper_BadReturn opOpAssign(string op)(IntWrapper_BadReturn rhs)
1162         {
1163             mixin("this.value " ~ op ~ "= rhs.value;");
1164 
1165             return IntWrapper_BadReturn(rhs.value);
1166         }
1167 
1168         string toString() const { return to!string(value); }
1169     }
1170 
1171     assert(collectExceptionMsg(assertPred!"+="(IntWrapper_BadReturn(5),
1172                                                IntWrapper_BadReturn(2),
1173                                                IntWrapper_BadReturn(7))) ==
1174            "assertPred!\"+=\" failed: Return value of [5] += [2] was\n" ~
1175            "[2] instead of\n" ~
1176            "[7].");
1177 
1178     assert(collectExceptionMsg(assertPred!"+="(IntWrapper_BadReturn(5),
1179                                                IntWrapper_BadReturn(2),
1180                                                IntWrapper_BadReturn(7),
1181                                                "It failed!")) ==
1182            "assertPred!\"+=\" failed: Return value of [5] += [2] was\n" ~
1183            "[2] instead of\n" ~
1184            "[7]: It failed!");
1185 }));
1186 
1187 
1188 void assertPred(string pred, string msg = null, string file = __FILE__, size_t line = __LINE__, T)
1189                (T a)
1190     if(__traits(compiles, unaryFun!pred(a)) &&
1191        is(typeof(unaryFun!pred(a)) : bool) &&
1192        isPrintable!T)
1193 {
1194     if(!unaryFun!pred(a))
1195     {
1196         immutable tail = msg.empty ? "." : ": " ~ msg;
1197 
1198         throwException( new AssertError(format(`assertPred!"%s" failed: [%s] (a)%s`, pred, a, tail),
1199                                         file,
1200                                         line)
1201 		);
1202     }
1203 }
1204 
1205 mixin(unittestSemiTwistDLib("assertPred: unaryFun", q{
1206 	autoThrow = true;
1207 
1208     assertNotThrown!AssertError(assertPred!"a == 1"(1));
1209     assertNotThrown!AssertError(assertPred!"a"(true));
1210     assertNotThrown!AssertError(assertPred!"!a"(false));
1211 
1212     assertThrown!AssertError(assertPred!"a == 1"(2));
1213     assertThrown!AssertError(assertPred!"a"(false));
1214     assertThrown!AssertError(assertPred!"!a"(true));
1215 
1216     assertPred!"=="(collectExceptionMsg(assertPred!"a == 1"(2)),
1217                     `assertPred!"a == 1" failed: [2] (a).`);
1218     assertPred!"=="(collectExceptionMsg(assertPred!("a == 1", "It failed!")(2)),
1219                     `assertPred!"a == 1" failed: [2] (a): It failed!`);
1220 
1221     //Test default arguments.
1222     assertPred!"a == 7"(7);
1223     assertPred!("a == 7", "msg")(7);
1224     assertPred!("a == 7", "msg", "file")(7);
1225     assertPred!("a == 7", "msg", "file", 42)(7);
1226 
1227     //Verify Examples.
1228     assertPred!"a == 1"(1);
1229 
1230     assertPred!"a * 2.0 == 4.0"(2);
1231 
1232     assert(collectExceptionMsg(assertPred!"a == 1"(2)),
1233            `assertPred!"a == 1" failed: [2] (a).`);
1234 
1235     assert(collectExceptionMsg(assertPred!("a * 2.0 == 4.0", "Woe is me!")(7)),
1236            `assertPred!"a * 2.0 == 4.0" failed: [7] (a): Woe is me!`);
1237 }));
1238 
1239 
1240 void assertPred(string pred, string msg = null, string file = __FILE__, size_t line = __LINE__, T, U)
1241                (T a, U b)
1242     if(__traits(compiles, binaryFun!pred(a, b)) &&
1243        is(typeof(binaryFun!pred(a, b)) : bool) &&
1244        isPrintable!T)
1245 {
1246     if(!binaryFun!pred(a, b))
1247     {
1248         immutable tail = msg.empty ? "." : ": " ~ msg;
1249 
1250         throwException( new AssertError(format(`assertPred!"%s" failed: [%s] (a), [%s] (b)%s`, pred, a, b, tail),
1251                                         file,
1252                                         line)
1253 		);
1254     }
1255 }
1256 
1257 mixin(unittestSemiTwistDLib("assertPred: binaryFun", q{
1258 	autoThrow = true;
1259 
1260     assertNotThrown!AssertError(assertPred!"a == b"(1, 1));
1261     assertNotThrown!AssertError(assertPred!"a * b == 2.0"(1, 2.0));
1262 
1263     assertThrown!AssertError(assertPred!"a == b"(1, 2));
1264     assertThrown!AssertError(assertPred!"a * b == 2.0"(2, 2.0));
1265 
1266     assertPred!"=="(collectExceptionMsg(assertPred!"a == b"(1, 2)),
1267                     `assertPred!"a == b" failed: [1] (a), [2] (b).`);
1268     assertPred!"=="(collectExceptionMsg(assertPred!("a == b", "It failed!")(1, 2)),
1269                     `assertPred!"a == b" failed: [1] (a), [2] (b): It failed!`);
1270 
1271     //Test default arguments.
1272     assertPred!"a == b"(7, 7);
1273     assertPred!("a == b", "msg")(7, 7);
1274     assertPred!("a == b", "msg", "file")(7, 7);
1275     assertPred!("a == b", "msg", "file", 42)(7, 7);
1276 
1277     //Verify Examples.
1278     assertPred!"a == b"(42, 42);
1279 
1280     assertPred!`a ~ b == "hello world"`("hello ", "world");
1281 
1282     assertPred!"=="(collectExceptionMsg(assertPred!"a == b"(1, 2)),
1283                     `assertPred!"a == b" failed: [1] (a), [2] (b).`);
1284 
1285     assertPred!"=="(collectExceptionMsg(assertPred!("a * b == 7", "It failed!")(2, 3)),
1286                     `assertPred!"a * b == 7" failed: [2] (a), [3] (b): It failed!`);
1287 }));
1288 
1289 
1290 void assertPred(alias pred, string msg = null, string file = __FILE__, size_t line = __LINE__, T...)
1291                (T args)
1292     if(isCallable!pred &&
1293        is(ReturnType!pred == bool) &&
1294        __traits(compiles, pred(args)) &&
1295        isPrintable!T)
1296 {
1297     immutable result = pred(args);
1298 
1299     if(!result)
1300     {
1301         string argsStr;
1302 
1303         if(args.length > 0)
1304         {
1305             foreach(value; args)
1306                 argsStr ~= format("[%s], ", to!string(value));
1307 
1308             argsStr.popBackN(", ".length);
1309         }
1310         else
1311             argsStr = "none";
1312 
1313         immutable tail = msg.empty ? "." : ": " ~ msg;
1314 
1315         throwException( new AssertError(format("assertPred failed: arguments: %s%s", argsStr, tail), file, line) );
1316     }
1317 }
1318 
1319 mixin(unittestSemiTwistDLib("assertPred: Delegates", q{
1320 	autoThrow = true;
1321 
1322     assertNotThrown!AssertError(assertPred!({return true;})());
1323     assertNotThrown!AssertError(assertPred!((bool a){return a;})(true));
1324     assertNotThrown!AssertError(assertPred!((int a, int b){return a == b;})(5, 5));
1325     assertNotThrown!AssertError(assertPred!((int a, int b, int c){return a == b && b == c;})(5, 5, 5));
1326     assertNotThrown!AssertError(assertPred!((int a, int b, int c, float d){return a * b < c * d;})(2, 4, 5, 1.7));
1327 
1328     assertThrown!AssertError(assertPred!({return false;})());
1329     assertThrown!AssertError(assertPred!((bool a){return a;})(false));
1330     assertThrown!AssertError(assertPred!((int a, int b){return a == b;})(5, 6));
1331     assertThrown!AssertError(assertPred!((int a, int b, int c){return a == b && b == c;})(5, 5, 6));
1332     assertThrown!AssertError(assertPred!((int a, int b, int c, float d){return a * b < c * d;})(3, 4, 5, 1.7));
1333 
1334     //Test default arguments.
1335     assertPred!((int a, int b){return a == b;})(7, 7);
1336     assertPred!((int a, int b){return a == b;}, "msg")(7, 7);
1337     assertPred!((int a, int b){return a == b;}, "msg", "file")(7, 7);
1338     assertPred!((int a, int b){return a == b;}, "msg", "file", 42)(7, 7);
1339 
1340     //Verify Examples.
1341     assertPred!((int[] range, int i){return canFind(range, i);})([1, 5, 7, 2], 7);
1342 
1343     assertPred!((int a, int b, int c){return a == b && b == c;})(5, 5, 5);
1344 
1345     assert(collectExceptionMsg(assertPred!((int a, int b, int c, float d){return a * b < c * d;})
1346                                            (22, 4, 5, 1.7)) ==
1347            "assertPred failed: arguments: [22], [4], [5], [1.7].");
1348 
1349 	// Crashes DMD 2.054 (DMD Issue #6351):
1350     //assert(collectExceptionMsg(assertPred!((string[] s...){return canFind(s, "hello");}, "Failure!")
1351     //                                      ("goodbye", "old", "friend")) ==
1352     //       "assertPred failed: arguments: [goodbye], [old], [friend]: Failure!");
1353 	
1354 }));
1355 
1356 //==============================================================================
1357 // Private Section.
1358 //
1359 // Note: assertNotThrown, assertThrown and collectExceptionMsg are included in
1360 // this module because they're used by assertPred's unittests and haven't been
1361 // added to Phobos just yet. But they're private becuase they're going to be
1362 // in Phobos soon.
1363 //==============================================================================
1364 private:
1365 
1366 void assertNotThrown(T : Throwable = Exception, F)
1367                     (lazy F funcToCall, string msg = null, string file = __FILE__, size_t line = __LINE__)
1368 {
1369     try
1370         funcToCall();
1371     catch(T t)
1372     {
1373         immutable tail = msg.empty ? "." : ": " ~ msg;
1374 
1375         throwException( new AssertError(format("assertNotThrown failed: %s was thrown%s", T.stringof, tail), file, line, t) );
1376     }
1377 }
1378 
1379 mixin(unittestSemiTwistDLib("private assertNotThrown", q{
1380 	autoThrow = true;
1381 	
1382     void throwEx(Throwable t)
1383     {
1384         throw t;
1385     }
1386 
1387     void nothrowEx()
1388     {
1389     }
1390 
1391     try
1392         assertNotThrown!Exception(nothrowEx());
1393     catch(AssertError)
1394         assert(0);
1395 
1396     try
1397         assertNotThrown!Exception(nothrowEx(), "It's a message");
1398     catch(AssertError)
1399         assert(0);
1400 
1401     try
1402         assertNotThrown!AssertError(nothrowEx());
1403     catch(AssertError)
1404         assert(0);
1405 
1406     try
1407         assertNotThrown!AssertError(nothrowEx(), "It's a message");
1408     catch(AssertError)
1409         assert(0);
1410 
1411 
1412     {
1413         bool thrown = false;
1414         try
1415             assertNotThrown!Exception(throwEx(new Exception("It's an Exception")));
1416         catch(AssertError)
1417             thrown = true;
1418 
1419         assert(thrown);
1420     }
1421 
1422     {
1423         bool thrown = false;
1424         try
1425             assertNotThrown!Exception(throwEx(new Exception("It's an Exception")), "It's a message");
1426         catch(AssertError)
1427             thrown = true;
1428 
1429         assert(thrown);
1430     }
1431 
1432     {
1433         bool thrown = false;
1434         try
1435             assertNotThrown!AssertError(throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)));
1436         catch(AssertError)
1437             thrown = true;
1438 
1439         assert(thrown);
1440     }
1441 
1442     {
1443         bool thrown = false;
1444         try
1445             assertNotThrown!AssertError(throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)), "It's a message");
1446         catch(AssertError)
1447             thrown = true;
1448 
1449         assert(thrown);
1450     }
1451 
1452     //Verify Examples.
1453     assertNotThrown!DateTimeException(std.datetime.TimeOfDay(0, 0, 0));
1454     assertNotThrown!DateTimeException(std.datetime.TimeOfDay(12, 30, 27));
1455     assertNotThrown(std.datetime.TimeOfDay(23, 59, 59));  //Exception is default.
1456 
1457     assert(collectExceptionMsg(assertNotThrown!TimeException(std.datetime.TimeOfDay(12, 0, 60))) ==
1458            "assertNotThrown failed: TimeException was thrown.");
1459 
1460     assert(collectExceptionMsg(assertNotThrown!TimeException(std.datetime.TimeOfDay(25, 0, 0), "error!")) ==
1461            "assertNotThrown failed: TimeException was thrown: error!");
1462 }));
1463 
1464 
1465 void assertThrown(T : Throwable = Exception, F)
1466                  (lazy F funcToCall, string msg = null, string file = __FILE__, size_t line = __LINE__)
1467 {
1468     bool thrown = false;
1469 
1470     try
1471         funcToCall();
1472     catch(T t)
1473         thrown = true;
1474 
1475     if(!thrown)
1476     {
1477         immutable tail = msg.empty ? "." : ": " ~ msg;
1478 
1479         throwException( new AssertError(format("assertThrown failed: No %s was thrown%s", T.stringof, tail), file, line) );
1480     }
1481 }
1482 
1483 mixin(unittestSemiTwistDLib("private assertThrown", q{
1484 	autoThrow = true;
1485 	
1486     void throwEx(Throwable t)
1487     {
1488         throw t;
1489     }
1490 
1491     void nothrowEx()
1492     {
1493     }
1494 
1495     try
1496         assertThrown!Exception(throwEx(new Exception("It's an Exception")));
1497     catch(AssertError)
1498         assert(0);
1499 
1500     try
1501         assertThrown!Exception(throwEx(new Exception("It's an Exception")), "It's a message");
1502     catch(AssertError)
1503         assert(0);
1504 
1505     try
1506         assertThrown!AssertError(throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)));
1507     catch(AssertError)
1508         assert(0);
1509 
1510     try
1511         assertThrown!AssertError(throwEx(new AssertError("It's an AssertError", __FILE__, __LINE__)), "It's a message");
1512     catch(AssertError)
1513         assert(0);
1514 
1515 
1516     {
1517         bool thrown = false;
1518         try
1519             assertThrown!Exception(nothrowEx());
1520         catch(AssertError)
1521             thrown = true;
1522 
1523         assert(thrown);
1524     }
1525 
1526     {
1527         bool thrown = false;
1528         try
1529             assertThrown!Exception(nothrowEx(), "It's a message");
1530         catch(AssertError)
1531             thrown = true;
1532 
1533         assert(thrown);
1534     }
1535 
1536     {
1537         bool thrown = false;
1538         try
1539             assertThrown!AssertError(nothrowEx());
1540         catch(AssertError)
1541             thrown = true;
1542 
1543         assert(thrown);
1544     }
1545 
1546     {
1547         bool thrown = false;
1548         try
1549             assertThrown!AssertError(nothrowEx(), "It's a message");
1550         catch(AssertError)
1551             thrown = true;
1552 
1553         assert(thrown);
1554     }
1555 
1556     //Verify Examples.
1557     assertThrown!DateTimeException(std.datetime.TimeOfDay(-1, 15, 30));
1558     assertThrown!DateTimeException(std.datetime.TimeOfDay(12, 60, 30));
1559     assertThrown(std.datetime.TimeOfDay(12, 15, 60));  //Exception is default.
1560 
1561 
1562     assert(collectExceptionMsg(assertThrown!AssertError(std.datetime.TimeOfDay(12, 0, 0))) ==
1563            "assertThrown failed: No AssertError was thrown.");
1564 
1565     assert(collectExceptionMsg(assertThrown!AssertError(std.datetime.TimeOfDay(12, 0, 0), "error!")) ==
1566            "assertThrown failed: No AssertError was thrown: error!");
1567 }));
1568 
1569 
1570 string collectExceptionMsg(T)(lazy T funcCall)
1571 {
1572     try
1573     {
1574         funcCall();
1575 
1576         return cast(string)null;
1577     }
1578     catch(Throwable t)
1579         return t.msg;
1580 }
1581 
1582 mixin(unittestSemiTwistDLib("private collectExceptionMsg", q{
1583 	autoThrow = true;
1584 
1585     //Verify Example.
1586     void throwFunc() {throw new Exception("My Message.");}
1587     assert(collectExceptionMsg(throwFunc()) == "My Message.");
1588 
1589     void nothrowFunc() {}
1590     assert(collectExceptionMsg(nothrowFunc()) is null);
1591 }));
1592 
1593 
1594 /+
1595     Whether the given type can be converted to a string.
1596   +/
1597 template isPrintable(T...)
1598 {
1599     static if(T.length == 0)
1600         enum isPrintable = true;
1601     else static if(T.length == 1)
1602     {
1603         enum isPrintable = (!isArray!(T[0]) && __traits(compiles, to!string(T[0].init))) ||
1604                            (isArray!(T[0]) && __traits(compiles, to!string(T[0].init[0])));
1605     }
1606     else
1607     {
1608         enum isPrintable = isPrintable!(T[0]) && isPrintable!(T[1 .. $]);
1609     }
1610 }