root/trunk/src/semitwist/util/unittests.d

Revision 227, 56.8 kB (checked in by Abscissa, 10 months ago)

(NB) Now works with DMD 2.054.

Line 
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 = ( sectionName==""? "" : ": "~sectionName ).escapeDDQS();
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()
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()
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()
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()
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()
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() { 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() { 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()
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()
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()
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() { 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() { 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 }
Note: See TracBrowser for help on using the browser.