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