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