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__, in 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__, in 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)(scope auto ref V value, scope auto ref E expected, 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)
81                    (scope auto ref V value,
82                     scope auto ref E expected,
83                     string file = __FILE__,
84                     in 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)(in auto ref T value, string file = __FILE__, in 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)(in auto ref T value, string file = __FILE__, in 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)(in auto ref T value, in auto ref U container, string file = __FILE__, in 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)(in auto ref T value, U container, string file = __FILE__, in 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)(in auto ref T value, in auto ref U container,
226                          string file = __FILE__, in 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)(in auto ref T value, U container,
258                          string file = __FILE__, in 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__, in 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__, in 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__, in 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)(in string prefix, scope 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)(scope 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_(scope 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)(in string prefix, scope 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     import std.math: approxEqual;
589     if (!approxEqual(value, expected, maxRelDiff, maxAbsDiff))
590     {
591         const msg =
592             formatValueInItsOwnLine("Expected approx: ", expected) ~
593             formatValueInItsOwnLine("     Got       : ", value);
594         throw new UnitTestException(msg, file, line);
595     }
596 }
597 
598 ///
599 @safe unittest {
600     1.0.shouldApproxEqual(1.0001);
601 }
602 
603 
604 bool isEqual(V, E)(scope V value, scope E expected)
605     if(isInputRange!V && isStaticArray!E)
606 {
607     return isEqual(value, expected[]);
608 }
609 
610 
611 bool isEqual(V, E)(scope V value, scope E expected)
612     if (isInputRange!V && isInputRange!E && !isSomeString!V &&
613         is(typeof(isEqual(value.front, expected.front))))
614 {
615 
616     while (!value.empty && !expected.empty) {
617         if(!isEqual(value.front, expected.front)) return false;
618         value.popFront;
619         expected.popFront;
620     }
621 
622     return value.empty && expected.empty;
623 }
624 
625 bool isEqual(V, E)(scope V value, scope E expected)
626     if (isSomeString!V && isSomeString!E &&
627         is(typeof(isEqual(value.front, expected.front))))
628 {
629     if(value.length != expected.length) return false;
630     // prevent auto-decoding
631     foreach(i; 0 .. value.length)
632         if(value[i] != expected[i]) return false;
633 
634     return true;
635 }
636 
637 template IsField(A...) if(A.length == 1) {
638     enum IsField = __traits(compiles, A[0].init);
639 }
640 
641 
642 bool isEqual(V, E)(scope V value, scope E expected)
643     if (isObject!V && isObject!E)
644 {
645     import std.meta: staticMap, Filter, staticIndexOf;
646 
647     static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})),
648                   "Cannot compare instances of " ~ V.stringof ~
649                   " or " ~ E.stringof ~ " unless toString is overridden for both");
650 
651     if(value  is null && expected !is null) return false;
652     if(value !is null && expected  is null) return false;
653     if(value  is null && expected  is null) return true;
654 
655     // If it has opEquals, use it
656     static if(staticIndexOf!("opEquals", __traits(derivedMembers, V)) != -1) {
657         return value.opEquals(expected);
658     } else {
659 
660         template IsFieldOf(T, string s) {
661             static if(__traits(compiles, IsField!(typeof(__traits(getMember, T.init, s)))))
662                 enum IsFieldOf = IsField!(typeof(__traits(getMember, T.init, s)));
663             else
664                 enum IsFieldOf = false;
665         }
666 
667         auto members(T)(T obj) {
668             import std.typecons: Tuple;
669             import std.meta: staticMap;
670             import std.traits: Unqual;
671 
672             alias Member(string name) = typeof(__traits(getMember, T, name));
673             alias IsFieldOfT(string s) = IsFieldOf!(T, s);
674             alias FieldNames = Filter!(IsFieldOfT, __traits(allMembers, T));
675             alias FieldTypes = staticMap!(Member, FieldNames);
676 
677             Tuple!(staticMap!(Unqual, FieldTypes)) ret;
678             foreach(i, name; FieldNames)
679                 ret[i] = __traits(getMember, obj, name);
680 
681             return ret;
682         }
683 
684         static if(is(V == interface))
685             return false;
686         else
687             return members(value) == members(expected);
688     }
689 }
690 
691 
692 /**
693  * Verify that rng is empty.
694  * Throws: UnitTestException on failure.
695  */
696 void shouldBeEmpty(R)(in auto ref R rng, string file = __FILE__, in size_t line = __LINE__)
697 if (isInputRange!R)
698 {
699     import std.conv: text;
700     if (!rng.empty)
701         fail(text("Range not empty: ", rng), file, line);
702 }
703 
704 /**
705  * Verify that rng is empty.
706  * Throws: UnitTestException on failure.
707  */
708 void shouldBeEmpty(R)(auto ref shared(R) rng, string file = __FILE__, in size_t line = __LINE__)
709 if (isInputRange!R)
710 {
711     import std.conv: text;
712     if (!rng.empty)
713         fail(text("Range not empty: ", rng), file, line);
714 }
715 
716 
717 /**
718  * Verify that aa is empty.
719  * Throws: UnitTestException on failure.
720  */
721 void shouldBeEmpty(T)(auto ref T aa, string file = __FILE__, in size_t line = __LINE__)
722 if (isAssociativeArray!T)
723 {
724     //keys is @system
725     () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }();
726 }
727 
728 ///
729 @safe pure unittest
730 {
731     int[] ints;
732     string[] strings;
733     string[string] aa;
734 
735     shouldBeEmpty(ints);
736     shouldBeEmpty(strings);
737     shouldBeEmpty(aa);
738 
739     ints ~= 1;
740     strings ~= "foo";
741     aa["foo"] = "bar";
742 }
743 
744 
745 /**
746  * Verify that rng is not empty.
747  * Throws: UnitTestException on failure.
748  */
749 void shouldNotBeEmpty(R)(R rng, string file = __FILE__, in size_t line = __LINE__)
750 if (isInputRange!R)
751 {
752     if (rng.empty)
753         fail("Range empty", file, line);
754 }
755 
756 /**
757  * Verify that aa is not empty.
758  * Throws: UnitTestException on failure.
759  */
760 void shouldNotBeEmpty(T)(in auto ref T aa, string file = __FILE__, in size_t line = __LINE__)
761 if (isAssociativeArray!T)
762 {
763     //keys is @system
764     () @trusted{ if (aa.keys.empty)
765         fail("AA empty", file, line); }();
766 }
767 
768 ///
769 @safe pure unittest
770 {
771     int[] ints;
772     string[] strings;
773     string[string] aa;
774 
775     ints ~= 1;
776     strings ~= "foo";
777     aa["foo"] = "bar";
778 
779     shouldNotBeEmpty(ints);
780     shouldNotBeEmpty(strings);
781     shouldNotBeEmpty(aa);
782 }
783 
784 /**
785  * Verify that t is greater than u.
786  * Throws: UnitTestException on failure.
787  */
788 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
789                                string file = __FILE__, in size_t line = __LINE__)
790 {
791     import std.conv: text;
792     if (t <= u)
793         fail(text(t, " is not > ", u), file, line);
794 }
795 
796 ///
797 @safe pure unittest
798 {
799     shouldBeGreaterThan(7, 5);
800 }
801 
802 
803 /**
804  * Verify that t is smaller than u.
805  * Throws: UnitTestException on failure.
806  */
807 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
808                                string file = __FILE__, in size_t line = __LINE__)
809 {
810     import std.conv: text;
811     if (t >= u)
812         fail(text(t, " is not < ", u), file, line);
813 }
814 
815 ///
816 @safe pure unittest
817 {
818     shouldBeSmallerThan(5, 7);
819 }
820 
821 
822 
823 /**
824  * Verify that t and u represent the same set (ordering is not important).
825  * Throws: UnitTestException on failure.
826  */
827 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected, string file = __FILE__, in size_t line = __LINE__)
828 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
829 {
830     import std.algorithm: sort;
831     import std.array: array;
832 
833     if (!isSameSet(value, expected))
834     {
835         static if(__traits(compiles, sort(expected.array)))
836             const expPrintRange = sort(expected.array).array;
837         else
838             alias expPrintRange = expected;
839 
840         static if(__traits(compiles, sort(value.array)))
841             const actPrintRange = sort(value.array).array;
842         else
843             alias actPrintRange = value;
844 
845         const msg = formatValueInItsOwnLine("Expected: ", expPrintRange) ~
846                     formatValueInItsOwnLine("     Got: ", actPrintRange);
847         throw new UnitTestException(msg, file, line);
848     }
849 }
850 
851 ///
852 @safe pure unittest
853 {
854     import std.range: iota;
855 
856     auto inOrder = iota(4);
857     auto noOrder = [2, 3, 0, 1];
858     auto oops = [2, 3, 4, 5];
859 
860     inOrder.shouldBeSameSetAs(noOrder);
861     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
862 
863     struct Struct
864     {
865         int i;
866     }
867 
868     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
869 }
870 
871 private bool isSameSet(T, U)(auto ref T t, auto ref U u) {
872     import std.algorithm: canFind;
873 
874     //sort makes the element types have to implement opCmp
875     //instead, try one by one
876     auto ta = t.array;
877     auto ua = u.array;
878     if (ta.length != ua.length) return false;
879     foreach(element; ta)
880     {
881         if (!ua.canFind(element)) return false;
882     }
883 
884     return true;
885 }
886 
887 /**
888  * Verify that value and expected do not represent the same set (ordering is not important).
889  * Throws: UnitTestException on failure.
890  */
891 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected, string file = __FILE__, in size_t line = __LINE__)
892 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
893 {
894     if (isSameSet(value, expected))
895     {
896         const msg = ["Value:",
897                      formatValueInItsOwnLine("", value).join(""),
898                      "is not expected to be equal to:",
899                      formatValueInItsOwnLine("", expected).join("")
900             ];
901         throw new UnitTestException(msg, file, line);
902     }
903 }
904 
905 
906 ///
907 @safe pure unittest
908 {
909     auto inOrder = iota(4);
910     auto noOrder = [2, 3, 0, 1];
911     auto oops = [2, 3, 4, 5];
912 
913     inOrder.shouldNotBeSameSetAs(oops);
914     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
915 }
916 
917 
918 
919 
920 /**
921    If two strings represent the same JSON regardless of formatting
922  */
923 void shouldBeSameJsonAs(in string actual,
924                         in string expected,
925                         string file = __FILE__,
926                         in size_t line = __LINE__)
927     @trusted // not @safe pure due to parseJSON
928 {
929     import std.json: parseJSON, JSONException;
930 
931     auto parse(in string str) {
932         try
933             return str.parseJSON;
934         catch(JSONException ex)
935             throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line);
936     }
937 
938     parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line);
939 }
940 
941 ///
942 @safe unittest { // not pure because parseJSON isn't pure
943     `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`);
944     `{"foo":    "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`);
945     `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException;
946     try
947         `oops`.shouldBeSameJsonAs(`oops`);
948     catch(Exception e)
949         assert(e.msg == "Error parsing JSON: Unexpected character 'o'. (Line 1:1)");
950 }
951 
952 
953 
954 auto should(V)(scope auto ref V value){
955 
956     import std.functional: forward;
957 
958     struct ShouldNot {
959 
960         bool opEquals(U)(auto ref U other,
961                          string file = __FILE__,
962                          in size_t line = __LINE__)
963         {
964             shouldNotEqual(forward!value, other, file, line);
965             return true;
966         }
967 
968         void opBinary(string op, R)(R range,
969                                     string file = __FILE__,
970                                     in size_t line = __LINE__) const if(op == "in") {
971             shouldNotBeIn(forward!value, range, file, line);
972         }
973 
974         void opBinary(string op, R)(R expected,
975                                     string file = __FILE__,
976                                     in size_t line = __LINE__) const
977             if(op == "~" && isInputRange!R)
978         {
979             import std.conv: text;
980 
981             bool failed;
982 
983             try
984                 shouldBeSameSetAs(forward!value, expected);
985             catch(UnitTestException)
986                 failed = true;
987 
988             if(!failed)
989                 fail(text(value, " should not be the same set as ", expected),
990                      file, line);
991         }
992 
993         void opBinary(string op, E)
994                      (in E expected, string file = __FILE__, size_t line = __LINE__)
995             if (isFloatingPoint!E)
996         {
997             import std.conv: text;
998 
999             bool failed;
1000 
1001             try
1002                 shouldApproxEqual(forward!value, expected);
1003             catch(UnitTestException)
1004                 failed = true;
1005 
1006             if(!failed)
1007                 fail(text(value, " should not be approximately equal to ", expected),
1008                      file, line);
1009         }
1010     }
1011 
1012     struct Should {
1013 
1014         bool opEquals(U)(auto ref U other,
1015                          string file = __FILE__,
1016                          in size_t line = __LINE__)
1017         {
1018             shouldEqual(forward!value, other, file, line);
1019             return true;
1020         }
1021 
1022         void opBinary(string op, R)(R range,
1023                                     string file = __FILE__,
1024                                     in size_t line = __LINE__) const
1025             if(op == "in")
1026         {
1027             shouldBeIn(forward!value, range, file, line);
1028         }
1029 
1030         void opBinary(string op, R)(R range,
1031                                     string file = __FILE__,
1032                                     in size_t line = __LINE__) const
1033             if(op == "~" && isInputRange!R)
1034         {
1035             shouldBeSameSetAs(forward!value, range, file, line);
1036         }
1037 
1038         void opBinary(string op, E)
1039                      (in E expected, string file = __FILE__, size_t line = __LINE__)
1040             if (isFloatingPoint!E)
1041         {
1042             shouldApproxEqual(forward!value, expected, 1e-2, 1e-5, file, line);
1043         }
1044 
1045         auto not() {
1046             return ShouldNot();
1047         }
1048     }
1049 
1050     return Should();
1051 }
1052 
1053 
1054 
1055 T be(T)(T sh) {
1056     return sh;
1057 }
1058 
1059 ///
1060 @safe pure unittest {
1061     1.should.be == 1;
1062     1.should.not.be == 2;
1063     1.should.be in [1, 2, 3];
1064     4.should.not.be in [1, 2, 3];
1065 }
1066 
1067 
1068 /**
1069    Asserts that `lowerBound` <= `actual` < `upperBound`
1070  */
1071 void shouldBeBetween(A, L, U)
1072     (auto ref A actual,
1073      auto ref L lowerBound,
1074      auto ref U upperBound,
1075      string file = __FILE__,
1076      in size_t line = __LINE__)
1077 {
1078     import std.conv: text;
1079     if(actual < lowerBound || actual >= upperBound)
1080         fail(text(actual, " is not between ", lowerBound, " and ", upperBound), file, line);
1081 }