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 }
249 
250 
251 /**
252  * Verify that the value is not null.
253  * Throws: UnitTestException on failure
254  */
255 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__)
256 {
257     if (value is null)
258         fail("Value is null", file, line);
259 }
260 
261 ///
262 @safe pure unittest
263 {
264     class Foo
265     {
266         this(int i) { this.i = i; }
267         override string toString() const
268         {
269             import std.conv: to;
270             return i.to!string;
271         }
272         int i;
273     }
274 
275     shouldNotBeNull(new Foo(4)) ;
276     shouldEqual(new Foo(5), new Foo(5));
277     assertFail(shouldEqual(new Foo(5), new Foo(4)));
278     shouldNotEqual(new Foo(5), new Foo(4)) ;
279     assertFail(shouldNotEqual(new Foo(5), new Foo(5)));
280 }
281 
282 enum isLikeAssociativeArray(T, K) = is(typeof({
283     if(K.init in T) { }
284     if(K.init !in T) { }
285 }));
286 
287 static assert(isLikeAssociativeArray!(string[string], string));
288 static assert(!isLikeAssociativeArray!(string[string], int));
289 
290 
291 /**
292  * Verify that the value is in the container.
293  * Throws: UnitTestException on failure
294 */
295 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__)
296     if (isLikeAssociativeArray!(U, T))
297 {
298     import std.conv: to;
299 
300     if (value !in container)
301     {
302         fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file,
303             line);
304     }
305 }
306 
307 ///
308 @safe pure unittest {
309     5.shouldBeIn([5: "foo"]);
310 
311     struct AA {
312         int onlyKey;
313         bool opBinaryRight(string op)(in int key) const {
314             return key == onlyKey;
315         }
316     }
317 
318     5.shouldBeIn(AA(5));
319     assertFail(5.shouldBeIn(AA(4)));
320 }
321 
322 /**
323  * Verify that the value is in the container.
324  * Throws: UnitTestException on failure
325  */
326 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__)
327     if (!isLikeAssociativeArray!(U, T) && isInputRange!U)
328 {
329     import std.algorithm: find;
330     import std.conv: to;
331 
332     if (find(container, value).empty)
333     {
334         fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file,
335             line);
336     }
337 }
338 
339 ///
340 @safe pure unittest
341 {
342     shouldBeIn(4, [1, 2, 4]);
343     shouldBeIn("foo", ["foo" : 1]);
344 }
345 
346 
347 /**
348  * Verify that the value is not in the container.
349  * Throws: UnitTestException on failure
350  */
351 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container,
352                          in string file = __FILE__, in size_t line = __LINE__)
353     if (isLikeAssociativeArray!(U, T))
354 {
355     import std.conv: to;
356 
357     if (value in container)
358     {
359         fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file,
360             line);
361     }
362 }
363 
364 ///
365 @safe pure unittest {
366     5.shouldNotBeIn([4: "foo"]);
367 
368     struct AA {
369         int onlyKey;
370         bool opBinaryRight(string op)(in int key) const {
371             return key == onlyKey;
372         }
373     }
374 
375     5.shouldNotBeIn(AA(4));
376     assertFail(5.shouldNotBeIn(AA(5)));
377 }
378 
379 
380 /**
381  * Verify that the value is not in the container.
382  * Throws: UnitTestException on failure
383  */
384 void shouldNotBeIn(T, U)(in auto ref T value, U container,
385                          in string file = __FILE__, in size_t line = __LINE__)
386     if (!isLikeAssociativeArray!(U, T) && isInputRange!U)
387 {
388     import std.algorithm: find;
389     import std.conv: to;
390 
391     if (find(container, value).length > 0)
392     {
393         fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file,
394             line);
395     }
396 }
397 
398 ///
399 @safe unittest
400 {
401     shouldNotBeIn(3.5, [1.1, 2.2, 4.4]);
402     shouldNotBeIn(1.0, [2.0 : 1, 3.0 : 2]);
403 }
404 
405 /**
406  * Verify that expr throws the templated Exception class.
407  * This succeeds if the expression throws a child class of
408  * the template parameter.
409  * Throws: UnitTestException on failure (when expr does not
410  * throw the expected exception)
411  */
412 void shouldThrow(T : Throwable = Exception, E)
413                 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__)
414 {
415     import std.conv: text;
416     import std.stdio;
417 
418     () @trusted { // @trusted because of catching Throwable
419         try {
420             if (!threw!T(expr))
421                 fail("Expression did not throw", file, line);
422         } catch(Throwable t)
423             fail(text("Expression threw ", typeid(t), " instead of the expected ", T.stringof), file, line);
424     }();
425 }
426 
427 /**
428  * Verify that expr throws the templated Exception class.
429  * This only succeeds if the expression throws an exception of
430  * the exact type of the template parameter.
431  * Throws: UnitTestException on failure (when expr does not
432  * throw the expected exception)
433  */
434 void shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr,
435     in string file = __FILE__, in size_t line = __LINE__)
436 {
437     import std.conv: text;
438 
439     const threw = threw!T(expr);
440     if (!threw)
441         fail("Expression did not throw", file, line);
442 
443     //Object.opEquals is @system and impure
444     const sameType = () @trusted { return threw.typeInfo == typeid(T); }();
445     if (!sameType)
446         fail(text("Expression threw wrong type ", threw.typeInfo,
447             "instead of expected type ", typeid(T)), file, line);
448 }
449 
450 /**
451  * Verify that expr does not throw the templated Exception class.
452  * Throws: UnitTestException on failure
453  */
454 void shouldNotThrow(T: Throwable = Exception, E)(lazy E expr,
455     in string file = __FILE__, in size_t line = __LINE__)
456 {
457     if (threw!T(expr))
458         fail("Expression threw", file, line);
459 }
460 
461 /**
462  * Verify that an exception is thrown with the right message
463  */
464 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
465                                                           string msg,
466                                                           string file = __FILE__,
467                                                           size_t line = __LINE__) {
468     auto threw = threw!T(expr);
469     if (!threw)
470         fail("Expression did not throw", file, line);
471 
472     threw.throwable.msg.shouldEqual(msg, file, line);
473 }
474 
475 ///
476 @safe pure unittest {
477     void funcThrows(string msg) { throw new Exception(msg); }
478     funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar");
479     funcThrows("foo bar").shouldThrowWithMessage("foo bar");
480     assertFail(funcThrows("boo boo").shouldThrowWithMessage("foo bar"));
481 }
482 
483 
484 //@trusted because the user might want to catch a throwable
485 //that's not derived from Exception, such as RangeError
486 private auto threw(T : Throwable, E)(lazy E expr) @trusted
487 {
488 
489     struct ThrowResult
490     {
491         bool threw;
492         TypeInfo typeInfo;
493         immutable(T) throwable;
494 
495         T opCast(T)() const pure if (is(T == bool))
496         {
497             return threw;
498         }
499     }
500 
501     import std.stdio;
502     try
503     {
504         expr();
505     }
506     catch (T e)
507     {
508         return ThrowResult(true, typeid(e), cast(immutable)e);
509     }
510 
511     return ThrowResult(false);
512 }
513 
514 // can't be made pure because of throwExactly, which in turn
515 // can't be pure because of Object.opEquals
516 @safe unittest
517 {
518     class CustomException : Exception
519     {
520         this(string msg = "")
521         {
522             super(msg);
523         }
524     }
525 
526     class ChildException : CustomException
527     {
528         this(string msg = "")
529         {
530             super(msg);
531         }
532     }
533 
534     void throwCustom()
535     {
536         throw new CustomException();
537     }
538 
539     throwCustom.shouldThrow;
540     throwCustom.shouldThrow!CustomException;
541 
542     void throwChild()
543     {
544         throw new ChildException();
545     }
546 
547     throwChild.shouldThrow;
548     throwChild.shouldThrow!CustomException;
549     throwChild.shouldThrow!ChildException;
550     throwChild.shouldThrowExactly!ChildException;
551     try
552     {
553         throwChild.shouldThrowExactly!CustomException; //should not succeed
554         assert(0, "shouldThrowExactly failed");
555     }
556     catch (Exception ex)
557     {
558     }
559 }
560 
561 @safe pure unittest
562 {
563     void throwRangeError()
564     {
565         ubyte[] bytes;
566         bytes = bytes[1 .. $];
567     }
568 
569     import core.exception : RangeError;
570 
571     throwRangeError.shouldThrow!RangeError;
572 }
573 
574 @safe pure unittest {
575     import std.stdio;
576 
577     import core.exception: OutOfMemoryError;
578 
579     class CustomException : Exception {
580         this(string msg = "", in string file = __FILE__, in size_t line = __LINE__) { super(msg, file, line); }
581     }
582 
583     void func() { throw new CustomException("oh noes"); }
584 
585     func.shouldThrow!CustomException;
586     assertFail(func.shouldThrow!OutOfMemoryError);
587 }
588 
589 
590 void fail(in string output, in string file, in size_t line) @safe pure
591 {
592     throw new UnitTestException([output], file, line);
593 }
594 
595 
596 private string[] formatValue(T)(in string prefix, auto ref T value) {
597 
598     import std.conv: to;
599 
600     static if(isSomeString!T) {
601         // isSomeString is true for wstring and dstring,
602         // so call .to!string anyway
603         return [ prefix ~ `"` ~ value.to!string ~ `"`];
604     } else static if(isInputRange!T) {
605         return formatRange(prefix, value);
606     } else {
607         return [prefix ~ convertToString(value)];
608     }
609 }
610 
611 // helper function for non-copyable types
612 private string convertToString(T)(in auto ref T value) { // std.conv.to sometimes is @system
613     import std.conv: to;
614 
615     static if(__traits(compiles, value.to!string))
616         return () @trusted { return value.to!string; }();
617     else static if(__traits(compiles, value.toString))
618         return value.toString;
619     else
620         return T.stringof ~ "<cannot print>";
621 
622 
623 }
624 
625 private string[] formatRange(T)(in string prefix, T value) {
626     import std.conv: to;
627     import std.range: ElementType;
628     import std.algorithm: map, reduce, max;
629 
630     //some versions of `to` are @system
631     auto defaultLines = () @trusted { return [prefix ~ value.to!string]; }();
632 
633     static if (!isInputRange!(ElementType!T))
634         return defaultLines;
635     else
636     {
637         import std.array: array;
638         const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length).reduce!max;
639         const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5) || maxElementSize > 10;
640         if (!tooBigForOneLine)
641             return defaultLines;
642         return [prefix ~ "["] ~
643             value.map!(a => formatValue("              ", a).join("") ~ ",").array ~
644             "          ]";
645     }
646 }
647 
648 private enum isObject(T) = is(T == class) || is(T == interface);
649 
650 private bool isEqual(V, E)(in auto ref V value, in auto ref E expected)
651  if (!isObject!V &&
652      (!isInputRange!V || !isInputRange!E) &&
653      !isFloatingPoint!V && !isFloatingPoint!E &&
654      is(typeof(value == expected) == bool))
655 {
656     return value == expected;
657 }
658 
659 private bool isEqual(V, E)(in V value, in E expected)
660  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
661 {
662     return value == expected;
663 }
664 
665 @safe pure unittest {
666     assert(isEqual(1.0, 1.0));
667     assert(!isEqual(1.0, 1.0001));
668 }
669 
670 private bool isApproxEqual(V, E)(in V value, in E expected)
671  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
672 {
673     import std.math;
674     return approxEqual(value, expected);
675 }
676 
677 @safe unittest {
678     assert(isApproxEqual(1.0, 1.0));
679     assert(isApproxEqual(1.0, 1.0001));
680 }
681 
682 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, size_t line = __LINE__)
683  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
684 {
685     if (!isApproxEqual(value, expected))
686     {
687         const msg =
688             formatValue("Expected approx: ", expected) ~
689             formatValue("     Got       : ", value);
690         throw new UnitTestException(msg, file, line);
691     }
692 }
693 
694 ///
695 @safe unittest {
696     1.0.shouldApproxEqual(1.0001);
697 }
698 
699 
700 private bool isEqual(V, E)(V value, E expected)
701 if (!isObject!V && isInputRange!V && isInputRange!E && is(typeof(value.front == expected.front) == bool))
702 {
703     import std.algorithm: equal;
704     return equal(value, expected);
705 }
706 
707 private bool isEqual(V, E)(V value, E expected)
708 if (!isObject!V &&
709     isInputRange!V && isInputRange!E && !is(typeof(value.front == expected.front) == bool) &&
710     isInputRange!(ElementType!V) && isInputRange!(ElementType!E))
711 {
712     import std.algorithm: equal;
713 
714     while (!value.empty && !expected.empty)
715     {
716         if (!equal(value.front, expected.front))
717             return false;
718 
719         value.popFront;
720         expected.popFront;
721     }
722 
723     return value.empty && expected.empty;
724 }
725 
726 private bool isEqual(V, E)(V value, E expected)
727 if (isObject!V && isObject!E)
728 {
729     static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})),
730                   "Cannot compare instances of " ~ V.stringof ~
731                   " or " ~ E.stringof ~ " unless toString is overridden for both");
732 
733     return value.tupleof == expected.tupleof;
734 }
735 
736 
737 @safe pure unittest {
738     import std.conv: to;
739 
740     assert(isEqual(2, 2));
741     assert(!isEqual(2, 3));
742 
743     assert(isEqual(2.1, 2.1));
744     assert(!isEqual(2.1, 2.2));
745 
746     assert(isEqual("foo", "foo"));
747     assert(!isEqual("foo", "fooo"));
748 
749     assert(isEqual([1, 2], [1, 2]));
750     assert(!isEqual([1, 2], [1, 2, 3]));
751 
752     assert(isEqual(iota(2), [0, 1]));
753     assert(!isEqual(iota(2), [1, 2, 3]));
754 
755     assert(isEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]));
756     assert(isEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]));
757     assert(!isEqual([[0, 1], [0, 1, 4]], [iota(2), iota(3)]));
758     assert(!isEqual([[0, 1], [0]], [iota(2), iota(3)]));
759 
760     assert(isEqual([0: 1], [0: 1]));
761 
762     const constIntToInts = [1 : 2, 3 : 7, 9 : 345];
763     auto intToInts = [1 : 2, 3 : 7, 9 : 345];
764 
765     assert(isEqual(intToInts, constIntToInts));
766     assert(isEqual(constIntToInts, intToInts));
767 
768     class Foo
769     {
770         this(int i) { this.i = i; }
771         override string toString() const { return i.to!string; }
772         int i;
773     }
774 
775     assert(isEqual(new Foo(5), new Foo(5)));
776     assert(!isEqual(new Foo(5), new Foo(4)));
777 
778     ubyte[] arr;
779     assert(isEqual(arr, []));
780 }
781 
782 
783 private void assertFail(E)(lazy E expression, in string file = __FILE__, in size_t line = __LINE__)
784 {
785     import std.exception: assertThrown;
786     assertThrown!UnitTestException(expression, null, file, line);
787 }
788 
789 /**
790  * Verify that rng is empty.
791  * Throws: UnitTestException on failure.
792  */
793 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__)
794 if (isInputRange!R)
795 {
796     import std.conv: text;
797     if (!rng.empty)
798         fail(text("Range not empty: ", rng), file, line);
799 }
800 
801 /**
802  * Verify that rng is empty.
803  * Throws: UnitTestException on failure.
804  */
805 void shouldBeEmpty(R)(auto ref shared(R) rng, in string file = __FILE__, in size_t line = __LINE__)
806 if (isInputRange!R)
807 {
808     import std.conv: text;
809     if (!rng.empty)
810         fail(text("Range not empty: ", rng), file, line);
811 }
812 
813 
814 /**
815  * Verify that aa is empty.
816  * Throws: UnitTestException on failure.
817  */
818 void shouldBeEmpty(T)(auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
819 if (isAssociativeArray!T)
820 {
821     //keys is @system
822     () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }();
823 }
824 
825 ///
826 @safe pure unittest
827 {
828     int[] ints;
829     string[] strings;
830     string[string] aa;
831 
832     shouldBeEmpty(ints);
833     shouldBeEmpty(strings);
834     shouldBeEmpty(aa);
835 
836     ints ~= 1;
837     strings ~= "foo";
838     aa["foo"] = "bar";
839 
840     assertFail(shouldBeEmpty(ints));
841     assertFail(shouldBeEmpty(strings));
842     assertFail(shouldBeEmpty(aa));
843 }
844 
845 
846 /**
847  * Verify that rng is not empty.
848  * Throws: UnitTestException on failure.
849  */
850 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
851 if (isInputRange!R)
852 {
853     if (rng.empty)
854         fail("Range empty", file, line);
855 }
856 
857 /**
858  * Verify that aa is not empty.
859  * Throws: UnitTestException on failure.
860  */
861 void shouldNotBeEmpty(T)(in auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
862 if (isAssociativeArray!T)
863 {
864     //keys is @system
865     () @trusted{ if (aa.keys.empty)
866         fail("AA empty", file, line); }();
867 }
868 
869 ///
870 @safe pure unittest
871 {
872     int[] ints;
873     string[] strings;
874     string[string] aa;
875 
876     assertFail(shouldNotBeEmpty(ints));
877     assertFail(shouldNotBeEmpty(strings));
878     assertFail(shouldNotBeEmpty(aa));
879 
880     ints ~= 1;
881     strings ~= "foo";
882     aa["foo"] = "bar";
883 
884     shouldNotBeEmpty(ints);
885     shouldNotBeEmpty(strings);
886     shouldNotBeEmpty(aa);
887 }
888 
889 /**
890  * Verify that t is greater than u.
891  * Throws: UnitTestException on failure.
892  */
893 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
894                                in string file = __FILE__, in size_t line = __LINE__)
895 {
896     import std.conv: text;
897     if (t <= u)
898         fail(text(t, " is not > ", u), file, line);
899 }
900 
901 ///
902 @safe pure unittest
903 {
904     shouldBeGreaterThan(7, 5);
905     assertFail(shouldBeGreaterThan(5, 7));
906     assertFail(shouldBeGreaterThan(7, 7));
907 }
908 
909 
910 /**
911  * Verify that t is smaller than u.
912  * Throws: UnitTestException on failure.
913  */
914 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
915                                in string file = __FILE__, in size_t line = __LINE__)
916 {
917     import std.conv: text;
918     if (t >= u)
919         fail(text(t, " is not < ", u), file, line);
920 }
921 
922 ///
923 @safe pure unittest
924 {
925     shouldBeSmallerThan(5, 7);
926     assertFail(shouldBeSmallerThan(7, 5));
927     assertFail(shouldBeSmallerThan(7, 7));
928 }
929 
930 
931 
932 /**
933  * Verify that t and u represent the same set (ordering is not important).
934  * Throws: UnitTestException on failure.
935  */
936 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
937 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
938 {
939     if (!isSameSet(value, expected))
940     {
941         const msg = formatValue("Expected: ", expected) ~
942                     formatValue("     Got: ", value);
943         throw new UnitTestException(msg, file, line);
944     }
945 }
946 
947 ///
948 @safe pure unittest
949 {
950     auto inOrder = iota(4);
951     auto noOrder = [2, 3, 0, 1];
952     auto oops = [2, 3, 4, 5];
953 
954     inOrder.shouldBeSameSetAs(noOrder);
955     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
956 
957     struct Struct
958     {
959         int i;
960     }
961 
962     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
963 }
964 
965 private bool isSameSet(T, U)(auto ref T t, auto ref U u) {
966     import std.algorithm: canFind;
967 
968     //sort makes the element types have to implement opCmp
969     //instead, try one by one
970     auto ta = t.array;
971     auto ua = u.array;
972     if (ta.length != ua.length) return false;
973     foreach(element; ta)
974     {
975         if (!ua.canFind(element)) return false;
976     }
977 
978     return true;
979 }
980 
981 /**
982  * Verify that value and expected do not represent the same set (ordering is not important).
983  * Throws: UnitTestException on failure.
984  */
985 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
986 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
987 {
988     if (isSameSet(value, expected))
989     {
990         const msg = ["Value:",
991                      formatValue("", value).join(""),
992                      "is not expected to be equal to:",
993                      formatValue("", expected).join("")
994             ];
995         throw new UnitTestException(msg, file, line);
996     }
997 }
998 
999 
1000 ///
1001 @safe pure unittest
1002 {
1003     auto inOrder = iota(4);
1004     auto noOrder = [2, 3, 0, 1];
1005     auto oops = [2, 3, 4, 5];
1006 
1007     inOrder.shouldNotBeSameSetAs(oops);
1008     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
1009 }
1010 
1011 
1012 @safe pure unittest {
1013     "foo"w.shouldEqual("foo");
1014 }
1015 
1016 
1017 /**
1018    If two strings represent the same JSON regardless of formatting
1019  */
1020 void shouldBeSameJsonAs(in string actual,
1021                         in string expected,
1022                         in string file = __FILE__,
1023                         in size_t line = __LINE__)
1024     @trusted // not @safe pure due to parseJSON
1025 {
1026     import std.json: parseJSON, JSONException;
1027 
1028     auto parse(in string str) {
1029         try
1030             return str.parseJSON;
1031         catch(JSONException ex)
1032             throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line);
1033     }
1034 
1035     parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line);
1036 }
1037 
1038 ///
1039 @safe unittest { // not pure because parseJSON isn't pure
1040     `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`);
1041     `{"foo":    "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`);
1042     `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException;
1043 }
1044 
1045 @("Non-copyable types can be asserted on")
1046 @safe pure unittest {
1047 
1048     struct Move {
1049         int i;
1050         @disable this(this);
1051     }
1052 
1053     Move(5).shouldEqual(Move(5));
1054 }