1 /**
2  * This module implements custom assertions via $(D shouldXXX) functions
3  * that throw exceptions containing information about why the assertion
4  * failed.
5  */
6 
7 module unit_threaded.should;
8 
9 import std.traits; // too many to list
10 import std.range;
11 
12 
13 /**
14  * An exception to signal that a test case has failed.
15  */
16 class UnitTestException : Exception
17 {
18     this(in string msg, string file = __FILE__,
19          size_t line = __LINE__, Throwable next = null) @safe pure nothrow
20     {
21         this([msg], file, line, next);
22     }
23 
24     this(in string[] msgLines, string file = __FILE__,
25          size_t line = __LINE__, Throwable next = null) @safe pure nothrow
26     {
27         import std..string: join;
28         super(msgLines.join("\n"), next, file, line);
29         this.msgLines = msgLines;
30     }
31 
32     override string toString() @safe const pure
33     {
34         import std.algorithm: map;
35         return () @trusted { return msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n"); }();
36     }
37 
38 private:
39 
40     const string[] msgLines;
41 
42     string getOutputPrefix(in string file, in size_t line) @safe const pure
43     {
44         import std.conv: to;
45         return "    " ~ file ~ ":" ~ line.to!string ~ " - ";
46     }
47 }
48 
49 /**
50  * Verify that the condition is `true`.
51  * Throws: UnitTestException on failure.
52  */
53 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__)
54 {
55     shouldEqual(cast(bool)condition, true, file, line);
56 }
57 
58 ///
59 @safe pure unittest
60 {
61     shouldBeTrue(true);
62 }
63 
64 @safe pure unittest {
65     static struct Foo {
66         bool opCast(T: bool)() {
67             return true;
68         }
69     }
70     shouldBeTrue(Foo());
71 }
72 
73 /**
74  * Verify that the condition is `false`.
75  * Throws: UnitTestException on failure.
76  */
77 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__)
78 {
79     shouldEqual(cast(bool)condition, false, file, line);
80 }
81 
82 ///
83 @safe pure unittest
84 {
85     shouldBeFalse(false);
86 }
87 
88 @safe pure unittest {
89     static struct Foo {
90         bool opCast(T: bool)() {
91             return false;
92         }
93     }
94     shouldBeFalse(Foo());
95 }
96 
97 /**
98  * Verify that two values are the same.
99  * Floating point values are compared using $(D std.math.approxEqual).
100  * Throws: UnitTestException on failure
101  */
102 void shouldEqual(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
103 {
104     if (!isEqual(value, expected))
105     {
106         const msg = formatValue("Expected: ", expected) ~
107                     formatValue("     Got: ", value);
108         throw new UnitTestException(msg, file, line);
109     }
110 }
111 
112 ///
113 @safe pure unittest {
114     shouldEqual(true, true);
115     shouldEqual(false, false);
116     shouldEqual(1, 1) ;
117     shouldEqual("foo", "foo") ;
118     shouldEqual([2, 3], [2, 3]) ;
119 
120     shouldEqual(iota(3), [0, 1, 2]);
121     shouldEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]);
122     shouldEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]);
123     shouldEqual([iota(2), iota(3)], [[0, 1], [0, 1, 2]]);
124 
125 }
126 
127 ///
128 @safe unittest {
129     //impure comparisons
130     shouldEqual(1.0, 1.0) ;
131 }
132 
133 /**
134  * Verify that two values are not the same.
135  * Throws: UnitTestException on failure
136  */
137 void shouldNotEqual(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
138 {
139     if (isEqual(value, expected))
140     {
141         const msg = ["Value:",
142                      formatValue("", value).join(""),
143                      "is not expected to be equal to:",
144                      formatValue("", expected).join("")
145             ];
146         throw new UnitTestException(msg, file, line);
147     }
148 }
149 
150 ///
151 @safe pure unittest
152 {
153     shouldNotEqual(true, false);
154     shouldNotEqual(1, 2);
155     shouldNotEqual("f", "b");
156     shouldNotEqual([2, 3], [2, 3, 4]);
157 }
158 
159 ///
160 @safe unittest {
161     shouldNotEqual(1.0, 2.0);
162 }
163 
164 
165 @safe pure unittest {
166     import unit_threaded.asserts;
167 
168     assertExceptionMsg(3.shouldEqual(5),
169                        "    source/unit_threaded/should.d:123 - Expected: 5\n" ~
170                        "    source/unit_threaded/should.d:123 -      Got: 3");
171 
172     assertExceptionMsg("foo".shouldEqual("bar"),
173                        "    source/unit_threaded/should.d:123 - Expected: \"bar\"\n" ~
174                        "    source/unit_threaded/should.d:123 -      Got: \"foo\"");
175 
176     assertExceptionMsg([1, 2, 4].shouldEqual([1, 2, 3]),
177                        "    source/unit_threaded/should.d:123 - Expected: [1, 2, 3]\n" ~
178                        "    source/unit_threaded/should.d:123 -      Got: [1, 2, 4]");
179 
180     assertExceptionMsg([[0, 1, 2, 3, 4], [1], [2], [3], [4], [5]].shouldEqual([[0], [1], [2]]),
181                        "    source/unit_threaded/should.d:123 - Expected: [[0], [1], [2]]\n" ~
182                        "    source/unit_threaded/should.d:123 -      Got: [[0, 1, 2, 3, 4], [1], [2], [3], [4], [5]]");
183 
184     assertExceptionMsg([[0, 1, 2, 3, 4, 5], [1], [2], [3]].shouldEqual([[0], [1], [2]]),
185                        "    source/unit_threaded/should.d:123 - Expected: [[0], [1], [2]]\n" ~
186                        "    source/unit_threaded/should.d:123 -      Got: [[0, 1, 2, 3, 4, 5], [1], [2], [3]]");
187 
188 
189     assertExceptionMsg([[0, 1, 2, 3, 4, 5], [1], [2], [3], [4], [5]].shouldEqual([[0]]),
190                        "    source/unit_threaded/should.d:123 - Expected: [[0]]\n" ~
191                        "    source/unit_threaded/should.d:123 -      Got: [\n" ~
192                        "    source/unit_threaded/should.d:123 -               [0, 1, 2, 3, 4, 5],\n" ~
193                        "    source/unit_threaded/should.d:123 -               [1],\n" ~
194                        "    source/unit_threaded/should.d:123 -               [2],\n" ~
195                        "    source/unit_threaded/should.d:123 -               [3],\n" ~
196                        "    source/unit_threaded/should.d:123 -               [4],\n" ~
197                        "    source/unit_threaded/should.d:123 -               [5],\n" ~
198                        "    source/unit_threaded/should.d:123 -           ]");
199 
200     assertExceptionMsg(1.shouldNotEqual(1),
201                        "    source/unit_threaded/should.d:123 - Value:\n" ~
202                        "    source/unit_threaded/should.d:123 - 1\n" ~
203                        "    source/unit_threaded/should.d:123 - is not expected to be equal to:\n" ~
204                        "    source/unit_threaded/should.d:123 - 1");
205 }
206 
207 @safe pure unittest
208 {
209     ubyte[] arr;
210     arr.shouldEqual([]);
211 }
212 
213 
214 @safe pure unittest
215 {
216     int[] ints = [1, 2, 3];
217     byte[] bytes = [1, 2, 3];
218     byte[] bytes2 = [1, 2, 4];
219     shouldEqual(ints, bytes);
220     shouldEqual(bytes, ints) ;
221     shouldNotEqual(ints, bytes2) ;
222 
223     const constIntToInts = [1 : 2, 3 : 7, 9 : 345];
224     auto intToInts = [1 : 2, 3 : 7, 9 : 345];
225     shouldEqual(intToInts, constIntToInts) ;
226     shouldEqual(constIntToInts, intToInts) ;
227 }
228 
229 @safe unittest {
230     shouldEqual([1 : 2.0, 2 : 4.0], [1 : 2.0, 2 : 4.0]) ;
231     shouldNotEqual([1 : 2.0, 2 : 4.0], [1 : 2.2, 2 : 4.0]) ;
232 }
233 
234 /**
235  * Verify that the value is null.
236  * Throws: UnitTestException on failure
237  */
238 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__)
239 {
240     if (value !is null)
241         fail("Value is not null", file, line);
242 }
243 
244 ///
245 @safe pure unittest
246 {
247     shouldBeNull(null);
248     assertFail(shouldBeNull(new int));
249 }
250 
251 
252 /**
253  * Verify that the value is not null.
254  * Throws: UnitTestException on failure
255  */
256 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__)
257 {
258     if (value is null)
259         fail("Value is null", file, line);
260 }
261 
262 ///
263 @safe pure unittest
264 {
265     class Foo
266     {
267         this(int i) { this.i = i; }
268         override string toString() const
269         {
270             import std.conv: to;
271             return i.to!string;
272         }
273         int i;
274     }
275 
276     shouldNotBeNull(new Foo(4));
277     assertFail(shouldNotBeNull(null));
278     shouldEqual(new Foo(5), new Foo(5));
279     assertFail(shouldEqual(new Foo(5), new Foo(4)));
280     shouldNotEqual(new Foo(5), new Foo(4)) ;
281     assertFail(shouldNotEqual(new Foo(5), new Foo(5)));
282 }
283 
284 enum isLikeAssociativeArray(T, K) = is(typeof({
285     if(K.init in T) { }
286     if(K.init !in T) { }
287 }));
288 
289 static assert(isLikeAssociativeArray!(string[string], string));
290 static assert(!isLikeAssociativeArray!(string[string], int));
291 
292 
293 /**
294  * Verify that the value is in the container.
295  * Throws: UnitTestException on failure
296 */
297 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
298     if (isLikeAssociativeArray!(U, T))
299 {
300     import std.conv: to;
301 
302     if (value !in container)
303     {
304         fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file,
305             line);
306     }
307 }
308 
309 ///
310 @safe pure unittest {
311     5.shouldBeIn([5: "foo"]);
312 
313     struct AA {
314         int onlyKey;
315         bool opBinaryRight(string op)(in int key) const {
316             return key == onlyKey;
317         }
318     }
319 
320     5.shouldBeIn(AA(5));
321     assertFail(5.shouldBeIn(AA(4)));
322 }
323 
324 /**
325  * Verify that the value is in the container.
326  * Throws: UnitTestException on failure
327  */
328 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
329     if (!isLikeAssociativeArray!(U, T) && isInputRange!U)
330 {
331     import std.algorithm: find;
332     import std.conv: to;
333 
334     if (find(container, value).empty)
335     {
336         fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file,
337             line);
338     }
339 }
340 
341 ///
342 @safe pure unittest
343 {
344     shouldBeIn(4, [1, 2, 4]);
345     shouldBeIn("foo", ["foo" : 1]);
346     assertFail("foo".shouldBeIn(["bar"]));
347 }
348 
349 
350 /**
351  * Verify that the value is not in the container.
352  * Throws: UnitTestException on failure
353  */
354 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container,
355                          in string file = __FILE__, in size_t line = __LINE__)
356     if (isLikeAssociativeArray!(U, T))
357 {
358     import std.conv: to;
359 
360     if (value in container)
361     {
362         fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file,
363             line);
364     }
365 }
366 
367 ///
368 @safe pure unittest {
369     5.shouldNotBeIn([4: "foo"]);
370 
371     struct AA {
372         int onlyKey;
373         bool opBinaryRight(string op)(in int key) const {
374             return key == onlyKey;
375         }
376     }
377 
378     5.shouldNotBeIn(AA(4));
379     assertFail(5.shouldNotBeIn(AA(5)));
380 }
381 
382 
383 /**
384  * Verify that the value is not in the container.
385  * Throws: UnitTestException on failure
386  */
387 void shouldNotBeIn(T, U)(in auto ref T value, U container,
388                          in string file = __FILE__, in size_t line = __LINE__)
389     if (!isLikeAssociativeArray!(U, T) && isInputRange!U)
390 {
391     import std.algorithm: find;
392     import std.conv: to;
393 
394     if (!find(container, value).empty)
395     {
396         fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file,
397             line);
398     }
399 }
400 
401 ///
402 @safe unittest
403 {
404     auto arrayRangeWithoutLength(T)(T[] array)
405     {
406         struct ArrayRangeWithoutLength(T)
407         {
408         private:
409             T[] array;
410         public:
411             T front() const @property
412             {
413                 return array[0];
414             }
415 
416             void popFront()
417             {
418                 array = array[1 .. $];
419             }
420 
421             bool empty() const @property
422             {
423                 return array.empty;
424             }
425         }
426         return ArrayRangeWithoutLength!T(array);
427     }
428     shouldNotBeIn(3.5, [1.1, 2.2, 4.4]);
429     shouldNotBeIn(1.0, [2.0 : 1, 3.0 : 2]);
430     shouldNotBeIn(1, arrayRangeWithoutLength([2, 3, 4]));
431     assertFail(1.shouldNotBeIn(arrayRangeWithoutLength([1, 2, 3])));
432     assertFail("foo".shouldNotBeIn(["foo"]));
433 }
434 
435 /**
436  * Verify that expr throws the templated Exception class.
437  * This succeeds if the expression throws a child class of
438  * the template parameter.
439  * Returns: The caught throwable.
440  * Throws: UnitTestException on failure (when expr does not
441  * throw the expected exception)
442  */
443 auto shouldThrow(T : Throwable = Exception, E)
444                 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__)
445 {
446     import std.conv: text;
447 
448     return () @trusted { // @trusted because of catching Throwable
449         try {
450            const result = threw!T(expr);
451            if (result) return result.throwable;
452         } catch(Throwable t)
453             fail(text("Expression threw ", typeid(t), " instead of the expected ", T.stringof), file, line);
454 
455         fail("Expression did not throw", file, line);
456         assert(0);
457     }();
458 }
459 
460 ///
461 @safe pure unittest {
462     import unit_threaded.asserts;
463     void funcThrows(string msg) { throw new Exception(msg); }
464     try {
465         auto exception = funcThrows("foo bar").shouldThrow;
466         assertEqual(exception.msg, "foo bar");
467     } catch(Exception e) {
468         assert(false, "should not have thrown anything and threw: " ~ e.msg);
469     }
470 }
471 
472 ///
473 @safe pure unittest {
474     import unit_threaded.asserts;
475     void func() {}
476     try {
477         func.shouldThrow;
478         assert(false, "Should never get here");
479     } catch(Exception e)
480         assertEqual(e.msg, "Expression did not throw");
481 }
482 
483 ///
484 @safe pure unittest {
485     import unit_threaded.asserts;
486     void funcAsserts() { assert(false); }
487     try {
488         funcAsserts.shouldThrow;
489         assert(false, "Should never get here");
490     } catch(Exception e)
491         assertEqual(e.msg, "Expression threw core.exception.AssertError instead of the expected Exception");
492 }
493 
494 
495 /**
496  * Verify that expr throws the templated Exception class.
497  * This only succeeds if the expression throws an exception of
498  * the exact type of the template parameter.
499  * Returns: The caught throwable.
500  * Throws: UnitTestException on failure (when expr does not
501  * throw the expected exception)
502  */
503 auto shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr,
504     in string file = __FILE__, in size_t line = __LINE__)
505 {
506     import std.conv: text;
507 
508     const threw = threw!T(expr);
509     if (!threw)
510         fail("Expression did not throw", file, line);
511 
512     //Object.opEquals is @system and impure
513     const sameType = () @trusted { return threw.typeInfo == typeid(T); }();
514     if (!sameType)
515         fail(text("Expression threw wrong type ", threw.typeInfo,
516             "instead of expected type ", typeid(T)), file, line);
517 
518     return threw.throwable;
519 }
520 
521 /**
522  * Verify that expr does not throw the templated Exception class.
523  * Throws: UnitTestException on failure
524  */
525 void shouldNotThrow(T: Throwable = Exception, E)(lazy E expr,
526     in string file = __FILE__, in size_t line = __LINE__)
527 {
528     if (threw!T(expr))
529         fail("Expression threw", file, line);
530 }
531 
532 unittest {
533     void func() {}
534     func.shouldNotThrow;
535     void funcThrows() { throw new Exception("oops"); }
536     assertFail(shouldNotThrow(funcThrows));
537 }
538 
539 /**
540  * Verify that an exception is thrown with the right message
541  */
542 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
543                                                           string msg,
544                                                           string file = __FILE__,
545                                                           size_t line = __LINE__) {
546     auto threw = threw!T(expr);
547     if (!threw)
548         fail("Expression did not throw", file, line);
549 
550     threw.throwable.msg.shouldEqual(msg, file, line);
551 }
552 
553 ///
554 @safe pure unittest {
555     void funcThrows(string msg) { throw new Exception(msg); }
556     funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar");
557     funcThrows("foo bar").shouldThrowWithMessage("foo bar");
558     assertFail(funcThrows("boo boo").shouldThrowWithMessage("foo bar"));
559     void func() {}
560     assertFail(func.shouldThrowWithMessage("oops"));
561 }
562 
563 
564 //@trusted because the user might want to catch a throwable
565 //that's not derived from Exception, such as RangeError
566 private auto threw(T : Throwable, E)(lazy E expr) @trusted
567 {
568 
569     static struct ThrowResult
570     {
571         bool threw;
572         TypeInfo typeInfo;
573         immutable(T) throwable;
574 
575         T opCast(T)() @safe @nogc const pure if (is(T == bool))
576         {
577             return threw;
578         }
579     }
580 
581     import std.stdio;
582     try
583     {
584         expr();
585     }
586     catch (T e)
587     {
588         return ThrowResult(true, typeid(e), cast(immutable)e);
589     }
590 
591     return ThrowResult(false);
592 }
593 
594 // can't be made pure because of throwExactly, which in turn
595 // can't be pure because of Object.opEquals
596 @safe unittest
597 {
598     class CustomException : Exception
599     {
600         this(string msg = "")
601         {
602             super(msg);
603         }
604     }
605 
606     class ChildException : CustomException
607     {
608         this(string msg = "")
609         {
610             super(msg);
611         }
612     }
613 
614     void throwCustom()
615     {
616         throw new CustomException();
617     }
618 
619     throwCustom.shouldThrow;
620     throwCustom.shouldThrow!CustomException;
621 
622     void throwChild()
623     {
624         throw new ChildException();
625     }
626 
627     throwChild.shouldThrow;
628     throwChild.shouldThrow!CustomException;
629     throwChild.shouldThrow!ChildException;
630     throwChild.shouldThrowExactly!ChildException;
631     try
632     {
633         throwChild.shouldThrowExactly!CustomException; //should not succeed
634         assert(0, "shouldThrowExactly failed");
635     }
636     catch (Exception ex)
637     {
638     }
639 
640     void doesntThrow() {}
641     assertFail(doesntThrow.shouldThrowExactly!Exception);
642 }
643 
644 @safe pure unittest
645 {
646     void throwRangeError()
647     {
648         ubyte[] bytes;
649         bytes = bytes[1 .. $];
650     }
651 
652     import core.exception : RangeError;
653 
654     throwRangeError.shouldThrow!RangeError;
655 }
656 
657 @safe pure unittest {
658     import std.stdio;
659 
660     import core.exception: OutOfMemoryError;
661 
662     class CustomException : Exception {
663         this(string msg = "", in string file = __FILE__, in size_t line = __LINE__) { super(msg, file, line); }
664     }
665 
666     void func() { throw new CustomException("oh noes"); }
667 
668     func.shouldThrow!CustomException;
669     assertFail(func.shouldThrow!OutOfMemoryError);
670 }
671 
672 
673 void fail(in string output, in string file, in size_t line) @safe pure
674 {
675     throw new UnitTestException([output], file, line);
676 }
677 
678 
679 private string[] formatValue(T)(in string prefix, auto ref T value) {
680 
681     import std.conv: to;
682 
683     static if(isSomeString!T) {
684         // isSomeString is true for wstring and dstring,
685         // so call .to!string anyway
686         return [ prefix ~ `"` ~ value.to!string ~ `"`];
687     } else static if(isInputRange!T) {
688         return formatRange(prefix, value);
689     } else {
690         return [prefix ~ convertToString(value)];
691     }
692 }
693 
694 // helper function for non-copyable types
695 private string convertToString(T)(in auto ref T value) { // std.conv.to sometimes is @system
696     import std.conv: to;
697     import std.traits: Unqual;
698 
699     static if(__traits(compiles, value.to!string))
700         return () @trusted { return value.to!string; }();
701     else static if(__traits(compiles, value.toString)) {
702         static if(isObject!T)
703             return () @trusted { return (cast(Unqual!T)value).toString; }();
704         else
705             return value.toString;
706     } else
707         return T.stringof ~ "<cannot print>";
708 }
709 
710 unittest {
711     import unit_threaded.asserts;
712     class Foo {
713         override string toString() @safe pure nothrow const {
714             return "Foo";
715         }
716     }
717 
718     auto foo = new const Foo;
719     assertEqual(foo.convertToString, "Foo");
720 }
721 
722 private string[] formatRange(T)(in string prefix, T value) {
723     import std.conv: to;
724     import std.range: ElementType;
725     import std.algorithm: map, reduce, max;
726 
727     //some versions of `to` are @system
728     auto defaultLines = () @trusted { return [prefix ~ value.to!string]; }();
729 
730     static if (!isInputRange!(ElementType!T))
731         return defaultLines;
732     else
733     {
734         import std.array: array;
735         const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length).reduce!max;
736         const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5) || maxElementSize > 10;
737         if (!tooBigForOneLine)
738             return defaultLines;
739         return [prefix ~ "["] ~
740             value.map!(a => formatValue("              ", a).join("") ~ ",").array ~
741             "          ]";
742     }
743 }
744 
745 private enum isObject(T) = is(T == class) || is(T == interface);
746 
747 private bool isEqual(V, E)(in auto ref V value, in auto ref E expected)
748  if (!isObject!V &&
749      (!isInputRange!V || !isInputRange!E) &&
750      !isFloatingPoint!V && !isFloatingPoint!E &&
751      is(typeof(value == expected) == bool))
752 {
753     return value == expected;
754 }
755 
756 private bool isEqual(V, E)(in V value, in E expected)
757  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
758 {
759     return value == expected;
760 }
761 
762 @safe pure unittest {
763     assert(isEqual(1.0, 1.0));
764     assert(!isEqual(1.0, 1.0001));
765 }
766 
767 private bool isApproxEqual(V, E)(in V value, in E expected)
768  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
769 {
770     import std.math;
771     return approxEqual(value, expected);
772 }
773 
774 @safe unittest {
775     assert(isApproxEqual(1.0, 1.0));
776     assert(isApproxEqual(1.0, 1.0001));
777 }
778 
779 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, size_t line = __LINE__)
780  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
781 {
782     if (!isApproxEqual(value, expected))
783     {
784         const msg =
785             formatValue("Expected approx: ", expected) ~
786             formatValue("     Got       : ", value);
787         throw new UnitTestException(msg, file, line);
788     }
789 }
790 
791 ///
792 @safe unittest {
793     1.0.shouldApproxEqual(1.0001);
794     assertFail(2.0.shouldApproxEqual(1.0));
795 }
796 
797 
798 private bool isEqual(V, E)(V value, E expected)
799     if (!isObject!V && isInputRange!V && isInputRange!E && !isSomeString!V &&
800         is(typeof(isEqual(value.front, expected.front))))
801 {
802 
803     while (!value.empty && !expected.empty) {
804         if(!isEqual(value.front, expected.front)) return false;
805         value.popFront;
806         expected.popFront;
807     }
808 
809     return value.empty && expected.empty;
810 }
811 
812 private bool isEqual(V, E)(V value, E expected)
813     if (!isObject!V && isInputRange!V && isInputRange!E && isSomeString!V && isSomeString!E &&
814         is(typeof(isEqual(value.front, expected.front))))
815 {
816     if(value.length != expected.length) return false;
817     // prevent auto-decoding
818     foreach(i; 0 .. value.length)
819         if(value[i] != expected[i]) return false;
820 
821     return true;
822 }
823 
824 
825 private bool isEqual(V, E)(V value, E expected)
826 if (isObject!V && isObject!E)
827 {
828     static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})),
829                   "Cannot compare instances of " ~ V.stringof ~
830                   " or " ~ E.stringof ~ " unless toString is overridden for both");
831 
832     return (value is null && expected is null) ||
833         (value !is null && expected !is null && value.tupleof == expected.tupleof);
834 }
835 
836 
837 @safe pure unittest {
838     import std.conv: to;
839 
840     assert(isEqual(2, 2));
841     assert(!isEqual(2, 3));
842 
843     assert(isEqual(2.1, 2.1));
844     assert(!isEqual(2.1, 2.2));
845 
846     assert(isEqual("foo", "foo"));
847     assert(!isEqual("foo", "fooo"));
848 
849     assert(isEqual([1, 2], [1, 2]));
850     assert(!isEqual([1, 2], [1, 2, 3]));
851 
852     assert(isEqual(iota(2), [0, 1]));
853     assert(!isEqual(iota(2), [1, 2, 3]));
854 
855     assert(isEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]));
856     assert(isEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]));
857     assert(!isEqual([[0, 1], [0, 1, 4]], [iota(2), iota(3)]));
858     assert(!isEqual([[0, 1], [0]], [iota(2), iota(3)]));
859 
860     assert(isEqual([0: 1], [0: 1]));
861 
862     const constIntToInts = [1 : 2, 3 : 7, 9 : 345];
863     auto intToInts = [1 : 2, 3 : 7, 9 : 345];
864 
865     assert(isEqual(intToInts, constIntToInts));
866     assert(isEqual(constIntToInts, intToInts));
867 
868     class Foo
869     {
870         this(int i) { this.i = i; }
871         override string toString() const { return i.to!string; }
872         int i;
873     }
874 
875     assert(isEqual(new Foo(5), new Foo(5)));
876     assert(!isEqual(new Foo(5), new Foo(4)));
877 
878     ubyte[] arr;
879     assert(isEqual(arr, []));
880 }
881 
882 
883 private void assertFail(E)(lazy E expression, in string file = __FILE__, in size_t line = __LINE__)
884 {
885     import std.exception: assertThrown;
886     assertThrown!UnitTestException(expression, null, file, line);
887 }
888 
889 /**
890  * Verify that rng is empty.
891  * Throws: UnitTestException on failure.
892  */
893 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__)
894 if (isInputRange!R)
895 {
896     import std.conv: text;
897     if (!rng.empty)
898         fail(text("Range not empty: ", rng), file, line);
899 }
900 
901 /**
902  * Verify that rng is empty.
903  * Throws: UnitTestException on failure.
904  */
905 void shouldBeEmpty(R)(auto ref shared(R) rng, in string file = __FILE__, in size_t line = __LINE__)
906 if (isInputRange!R)
907 {
908     import std.conv: text;
909     if (!rng.empty)
910         fail(text("Range not empty: ", rng), file, line);
911 }
912 
913 
914 /**
915  * Verify that aa is empty.
916  * Throws: UnitTestException on failure.
917  */
918 void shouldBeEmpty(T)(auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
919 if (isAssociativeArray!T)
920 {
921     //keys is @system
922     () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }();
923 }
924 
925 ///
926 @safe pure unittest
927 {
928     int[] ints;
929     string[] strings;
930     string[string] aa;
931 
932     shouldBeEmpty(ints);
933     shouldBeEmpty(strings);
934     shouldBeEmpty(aa);
935 
936     ints ~= 1;
937     strings ~= "foo";
938     aa["foo"] = "bar";
939 
940     assertFail(shouldBeEmpty(ints));
941     assertFail(shouldBeEmpty(strings));
942     assertFail(shouldBeEmpty(aa));
943 }
944 
945 
946 /**
947  * Verify that rng is not empty.
948  * Throws: UnitTestException on failure.
949  */
950 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
951 if (isInputRange!R)
952 {
953     if (rng.empty)
954         fail("Range empty", file, line);
955 }
956 
957 /**
958  * Verify that aa is not empty.
959  * Throws: UnitTestException on failure.
960  */
961 void shouldNotBeEmpty(T)(in auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
962 if (isAssociativeArray!T)
963 {
964     //keys is @system
965     () @trusted{ if (aa.keys.empty)
966         fail("AA empty", file, line); }();
967 }
968 
969 ///
970 @safe pure unittest
971 {
972     int[] ints;
973     string[] strings;
974     string[string] aa;
975 
976     assertFail(shouldNotBeEmpty(ints));
977     assertFail(shouldNotBeEmpty(strings));
978     assertFail(shouldNotBeEmpty(aa));
979 
980     ints ~= 1;
981     strings ~= "foo";
982     aa["foo"] = "bar";
983 
984     shouldNotBeEmpty(ints);
985     shouldNotBeEmpty(strings);
986     shouldNotBeEmpty(aa);
987 }
988 
989 /**
990  * Verify that t is greater than u.
991  * Throws: UnitTestException on failure.
992  */
993 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
994                                in string file = __FILE__, in size_t line = __LINE__)
995 {
996     import std.conv: text;
997     if (t <= u)
998         fail(text(t, " is not > ", u), file, line);
999 }
1000 
1001 ///
1002 @safe pure unittest
1003 {
1004     shouldBeGreaterThan(7, 5);
1005     assertFail(shouldBeGreaterThan(5, 7));
1006     assertFail(shouldBeGreaterThan(7, 7));
1007 }
1008 
1009 
1010 /**
1011  * Verify that t is smaller than u.
1012  * Throws: UnitTestException on failure.
1013  */
1014 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
1015                                in string file = __FILE__, in size_t line = __LINE__)
1016 {
1017     import std.conv: text;
1018     if (t >= u)
1019         fail(text(t, " is not < ", u), file, line);
1020 }
1021 
1022 ///
1023 @safe pure unittest
1024 {
1025     shouldBeSmallerThan(5, 7);
1026     assertFail(shouldBeSmallerThan(7, 5));
1027     assertFail(shouldBeSmallerThan(7, 7));
1028 }
1029 
1030 
1031 
1032 /**
1033  * Verify that t and u represent the same set (ordering is not important).
1034  * Throws: UnitTestException on failure.
1035  */
1036 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
1037 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
1038 {
1039     if (!isSameSet(value, expected))
1040     {
1041         const msg = formatValue("Expected: ", expected) ~
1042                     formatValue("     Got: ", value);
1043         throw new UnitTestException(msg, file, line);
1044     }
1045 }
1046 
1047 ///
1048 @safe pure unittest
1049 {
1050     auto inOrder = iota(4);
1051     auto noOrder = [2, 3, 0, 1];
1052     auto oops = [2, 3, 4, 5];
1053 
1054     inOrder.shouldBeSameSetAs(noOrder);
1055     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
1056 
1057     struct Struct
1058     {
1059         int i;
1060     }
1061 
1062     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
1063 }
1064 
1065 private bool isSameSet(T, U)(auto ref T t, auto ref U u) {
1066     import std.algorithm: canFind;
1067 
1068     //sort makes the element types have to implement opCmp
1069     //instead, try one by one
1070     auto ta = t.array;
1071     auto ua = u.array;
1072     if (ta.length != ua.length) return false;
1073     foreach(element; ta)
1074     {
1075         if (!ua.canFind(element)) return false;
1076     }
1077 
1078     return true;
1079 }
1080 
1081 /**
1082  * Verify that value and expected do not represent the same set (ordering is not important).
1083  * Throws: UnitTestException on failure.
1084  */
1085 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
1086 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
1087 {
1088     if (isSameSet(value, expected))
1089     {
1090         const msg = ["Value:",
1091                      formatValue("", value).join(""),
1092                      "is not expected to be equal to:",
1093                      formatValue("", expected).join("")
1094             ];
1095         throw new UnitTestException(msg, file, line);
1096     }
1097 }
1098 
1099 
1100 ///
1101 @safe pure unittest
1102 {
1103     auto inOrder = iota(4);
1104     auto noOrder = [2, 3, 0, 1];
1105     auto oops = [2, 3, 4, 5];
1106 
1107     inOrder.shouldNotBeSameSetAs(oops);
1108     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
1109 }
1110 
1111 
1112 @safe pure unittest {
1113     "foo"w.shouldEqual("foo");
1114 }
1115 
1116 
1117 /**
1118    If two strings represent the same JSON regardless of formatting
1119  */
1120 void shouldBeSameJsonAs(in string actual,
1121                         in string expected,
1122                         in string file = __FILE__,
1123                         in size_t line = __LINE__)
1124     @trusted // not @safe pure due to parseJSON
1125 {
1126     import std.json: parseJSON, JSONException;
1127 
1128     auto parse(in string str) {
1129         try
1130             return str.parseJSON;
1131         catch(JSONException ex)
1132             throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line);
1133     }
1134 
1135     parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line);
1136 }
1137 
1138 ///
1139 @safe unittest { // not pure because parseJSON isn't pure
1140     import unit_threaded.asserts;
1141     `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`);
1142     `{"foo":    "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`);
1143     `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException;
1144     try
1145         `oops`.shouldBeSameJsonAs(`oops`);
1146     catch(Exception e)
1147         assertEqual(e.msg, "Error parsing JSON: Unexpected character 'o'. (Line 1:1)");
1148 }
1149 
1150 @("Non-copyable types can be asserted on")
1151 @safe pure unittest {
1152 
1153     struct Move {
1154         int i;
1155         @disable this(this);
1156     }
1157 
1158     Move(5).shouldEqual(Move(5));
1159 }
1160 
1161 @("issue 88")
1162 @safe pure unittest {
1163 
1164     class C {
1165         int foo;
1166         override string toString() @safe pure nothrow const { return null; }
1167     }
1168 
1169     C c = null;
1170     c.shouldEqual(c);
1171     C null_;
1172     assertFail((new C).shouldEqual(null_));
1173 }
1174 
1175 @("issue 89")
1176 unittest {
1177     class C {
1178         override string toString() @safe pure nothrow const { return null; }
1179     }
1180 
1181     auto actual = new C;
1182     auto expected = new C;
1183 
1184     // these should both pass
1185     actual.shouldEqual(expected);      // passes: actual.tupleof == expected.tupleof
1186     [actual].shouldEqual([expected]);  // fails: actual != expected
1187 }
1188 
1189 @("non-const toString should compile")
1190 @safe pure unittest {
1191     class C {
1192         override string toString() @safe pure nothrow { return null; }
1193     }
1194     (new C).shouldEqual(new C);
1195 }
1196 
1197 @safe pure unittest {
1198     ['\xff'].shouldEqual(['\xff']);
1199 }
1200 
1201 @safe unittest {
1202     shouldEqual(new Object, new Object);
1203 }