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