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