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