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.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         string msg;
428         immutable(T) throwable;
429 
430         T opCast(T)() @safe @nogc const pure if (is(T == bool))
431         {
432             return threw;
433         }
434     }
435 
436     import std.stdio;
437     try
438     {
439         expr();
440     }
441     catch (T e)
442     {
443         return ThrowResult(true, typeid(e), e.msg.dup, cast(immutable)e);
444     }
445 
446     return ThrowResult(false);
447 }
448 
449 
450 
451 // Formats output in different lines
452 private string[] formatValueInItsOwnLine(T)(in string prefix, scope auto ref T value) {
453 
454     import std.conv: to;
455     import std.traits: isSomeString;
456     import std.range.primitives: isInputRange;
457 
458     static if(isSomeString!T) {
459         // isSomeString is true for wstring and dstring,
460         // so call .to!string anyway
461         return [ prefix ~ `"` ~ value.to!string ~ `"`];
462     } else static if(isInputRange!T) {
463         return formatRange(prefix, value);
464     } else {
465         return [prefix ~ convertToString(value)];
466     }
467 }
468 
469 // helper function for non-copyable types
470 string convertToString(T)(scope auto ref T value) { // std.conv.to sometimes is @system
471     import std.conv: to;
472     import std.traits: Unqual;
473 
474     static if(__traits(compiles, () @trusted { return value.to!string; }()))
475         return () @trusted { return value.to!string; }();
476     else static if(__traits(compiles, value.toString)) {
477         static if(isObject!T)
478             return () @trusted { return (cast(Unqual!T)value).toString; }();
479         else
480             return value.toString;
481     } else
482         return T.stringof ~ "<cannot print>";
483 }
484 
485 
486 private string[] formatRange(T)(in string prefix, scope auto ref T value) {
487     import std.conv: text;
488     import std.range: ElementType;
489     import std.algorithm: map, reduce, max;
490 
491     //some versions of `text` are @system
492     auto defaultLines = () @trusted { return [prefix ~ value.text]; }();
493 
494     static if (!isInputRange!(ElementType!T))
495         return defaultLines;
496     else
497     {
498         import std.array: array;
499         const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length).reduce!max;
500         const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5) || maxElementSize > 10;
501         if (!tooBigForOneLine)
502             return defaultLines;
503         return [prefix ~ "["] ~
504             value.map!(a => formatValueInItsOwnLine("              ", a).join("") ~ ",").array ~
505             "          ]";
506     }
507 }
508 
509 private enum isObject(T) = is(T == class) || is(T == interface);
510 
511 bool isEqual(V, E)(in auto ref V value, in auto ref E expected)
512  if (!isObject!V &&
513      !isFloatingPoint!V && !isFloatingPoint!E &&
514      is(typeof(value == expected) == bool))
515 {
516     return value == expected;
517 }
518 
519 bool isEqual(V, E)(in V value, in E expected)
520  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
521 {
522     return value == expected;
523 }
524 
525 
526 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__)
527  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
528 {
529     import std.math: approxEqual;
530     if (!approxEqual(value, expected, maxRelDiff, maxAbsDiff))
531     {
532         const msg =
533             formatValueInItsOwnLine("Expected approx: ", expected) ~
534             formatValueInItsOwnLine("     Got       : ", value);
535         throw new UnitTestException(msg, file, line);
536     }
537 }
538 
539 ///
540 @safe unittest {
541     1.0.shouldApproxEqual(1.0001);
542 }
543 
544 
545 bool isEqual(V, E)(scope V value, scope E expected)
546     if (!isObject!V && isInputRange!V && isInputRange!E && !isSomeString!V &&
547         is(typeof(isEqual(value.front, expected.front))))
548 {
549 
550     while (!value.empty && !expected.empty) {
551         if(!isEqual(value.front, expected.front)) return false;
552         value.popFront;
553         expected.popFront;
554     }
555 
556     return value.empty && expected.empty;
557 }
558 
559 bool isEqual(V, E)(scope V value, scope E expected)
560     if (!isObject!V && isInputRange!V && isInputRange!E && isSomeString!V && isSomeString!E &&
561         is(typeof(isEqual(value.front, expected.front))))
562 {
563     if(value.length != expected.length) return false;
564     // prevent auto-decoding
565     foreach(i; 0 .. value.length)
566         if(value[i] != expected[i]) return false;
567 
568     return true;
569 }
570 
571 template IsField(A...) if(A.length == 1) {
572     enum IsField = __traits(compiles, A[0].init);
573 }
574 
575 
576 bool isEqual(V, E)(scope V value, scope E expected)
577     if (isObject!V && isObject!E)
578 {
579     import std.meta: staticMap, Filter, staticIndexOf;
580 
581     static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})),
582                   "Cannot compare instances of " ~ V.stringof ~
583                   " or " ~ E.stringof ~ " unless toString is overridden for both");
584 
585     if(value  is null && expected !is null) return false;
586     if(value !is null && expected  is null) return false;
587     if(value  is null && expected  is null) return true;
588 
589     // If it has opEquals, use it
590     static if(staticIndexOf!("opEquals", __traits(derivedMembers, V)) != -1) {
591         pragma(msg, "Using opEquals for ", V, " and ", E);
592         return value.opEquals(expected);
593     } else {
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 /**
626  * Verify that rng is empty.
627  * Throws: UnitTestException on failure.
628  */
629 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__)
630 if (isInputRange!R)
631 {
632     import std.conv: text;
633     if (!rng.empty)
634         fail(text("Range not empty: ", rng), file, line);
635 }
636 
637 /**
638  * Verify that rng is empty.
639  * Throws: UnitTestException on failure.
640  */
641 void shouldBeEmpty(R)(auto ref shared(R) rng, in string file = __FILE__, in size_t line = __LINE__)
642 if (isInputRange!R)
643 {
644     import std.conv: text;
645     if (!rng.empty)
646         fail(text("Range not empty: ", rng), file, line);
647 }
648 
649 
650 /**
651  * Verify that aa is empty.
652  * Throws: UnitTestException on failure.
653  */
654 void shouldBeEmpty(T)(auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
655 if (isAssociativeArray!T)
656 {
657     //keys is @system
658     () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }();
659 }
660 
661 ///
662 @safe pure unittest
663 {
664     int[] ints;
665     string[] strings;
666     string[string] aa;
667 
668     shouldBeEmpty(ints);
669     shouldBeEmpty(strings);
670     shouldBeEmpty(aa);
671 
672     ints ~= 1;
673     strings ~= "foo";
674     aa["foo"] = "bar";
675 }
676 
677 
678 /**
679  * Verify that rng is not empty.
680  * Throws: UnitTestException on failure.
681  */
682 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
683 if (isInputRange!R)
684 {
685     if (rng.empty)
686         fail("Range empty", file, line);
687 }
688 
689 /**
690  * Verify that aa is not empty.
691  * Throws: UnitTestException on failure.
692  */
693 void shouldNotBeEmpty(T)(in auto ref T aa, in string file = __FILE__, in size_t line = __LINE__)
694 if (isAssociativeArray!T)
695 {
696     //keys is @system
697     () @trusted{ if (aa.keys.empty)
698         fail("AA empty", file, line); }();
699 }
700 
701 ///
702 @safe pure unittest
703 {
704     int[] ints;
705     string[] strings;
706     string[string] aa;
707 
708     ints ~= 1;
709     strings ~= "foo";
710     aa["foo"] = "bar";
711 
712     shouldNotBeEmpty(ints);
713     shouldNotBeEmpty(strings);
714     shouldNotBeEmpty(aa);
715 }
716 
717 /**
718  * Verify that t is greater than u.
719  * Throws: UnitTestException on failure.
720  */
721 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u,
722                                in string file = __FILE__, in size_t line = __LINE__)
723 {
724     import std.conv: text;
725     if (t <= u)
726         fail(text(t, " is not > ", u), file, line);
727 }
728 
729 ///
730 @safe pure unittest
731 {
732     shouldBeGreaterThan(7, 5);
733 }
734 
735 
736 /**
737  * Verify that t is smaller than u.
738  * Throws: UnitTestException on failure.
739  */
740 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u,
741                                in string file = __FILE__, in size_t line = __LINE__)
742 {
743     import std.conv: text;
744     if (t >= u)
745         fail(text(t, " is not < ", u), file, line);
746 }
747 
748 ///
749 @safe pure unittest
750 {
751     shouldBeSmallerThan(5, 7);
752 }
753 
754 
755 
756 /**
757  * Verify that t and u represent the same set (ordering is not important).
758  * Throws: UnitTestException on failure.
759  */
760 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
761 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
762 {
763     if (!isSameSet(value, expected))
764     {
765         const msg = formatValueInItsOwnLine("Expected: ", expected) ~
766                     formatValueInItsOwnLine("     Got: ", value);
767         throw new UnitTestException(msg, file, line);
768     }
769 }
770 
771 ///
772 @safe pure unittest
773 {
774     import std.range: iota;
775 
776     auto inOrder = iota(4);
777     auto noOrder = [2, 3, 0, 1];
778     auto oops = [2, 3, 4, 5];
779 
780     inOrder.shouldBeSameSetAs(noOrder);
781     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
782 
783     struct Struct
784     {
785         int i;
786     }
787 
788     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
789 }
790 
791 private bool isSameSet(T, U)(auto ref T t, auto ref U u) {
792     import std.algorithm: canFind;
793 
794     //sort makes the element types have to implement opCmp
795     //instead, try one by one
796     auto ta = t.array;
797     auto ua = u.array;
798     if (ta.length != ua.length) return false;
799     foreach(element; ta)
800     {
801         if (!ua.canFind(element)) return false;
802     }
803 
804     return true;
805 }
806 
807 /**
808  * Verify that value and expected do not represent the same set (ordering is not important).
809  * Throws: UnitTestException on failure.
810  */
811 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__)
812 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
813 {
814     if (isSameSet(value, expected))
815     {
816         const msg = ["Value:",
817                      formatValueInItsOwnLine("", value).join(""),
818                      "is not expected to be equal to:",
819                      formatValueInItsOwnLine("", expected).join("")
820             ];
821         throw new UnitTestException(msg, file, line);
822     }
823 }
824 
825 
826 ///
827 @safe pure unittest
828 {
829     auto inOrder = iota(4);
830     auto noOrder = [2, 3, 0, 1];
831     auto oops = [2, 3, 4, 5];
832 
833     inOrder.shouldNotBeSameSetAs(oops);
834     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
835 }
836 
837 
838 
839 
840 /**
841    If two strings represent the same JSON regardless of formatting
842  */
843 void shouldBeSameJsonAs(in string actual,
844                         in string expected,
845                         in string file = __FILE__,
846                         in size_t line = __LINE__)
847     @trusted // not @safe pure due to parseJSON
848 {
849     import std.json: parseJSON, JSONException;
850 
851     auto parse(in string str) {
852         try
853             return str.parseJSON;
854         catch(JSONException ex)
855             throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line);
856     }
857 
858     parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line);
859 }
860 
861 ///
862 @safe unittest { // not pure because parseJSON isn't pure
863     `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`);
864     `{"foo":    "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`);
865     `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException;
866     try
867         `oops`.shouldBeSameJsonAs(`oops`);
868     catch(Exception e)
869         assert(e.msg == "Error parsing JSON: Unexpected character 'o'. (Line 1:1)");
870 }
871 
872 
873 
874 auto should(V)(scope auto ref V value){
875 
876     import std.functional: forward;
877 
878     struct ShouldNot {
879 
880         bool opEquals(U)(auto ref U other,
881                          in string file = __FILE__,
882                          in size_t line = __LINE__)
883         {
884             shouldNotEqual(forward!value, other, file, line);
885             return true;
886         }
887 
888         void opBinary(string op, R)(R range,
889                                     in string file = __FILE__,
890                                     in size_t line = __LINE__) const if(op == "in") {
891             shouldNotBeIn(forward!value, range, file, line);
892         }
893 
894         void opBinary(string op, R)(R range,
895                                     in string file = __FILE__,
896                                     in size_t line = __LINE__) const
897             if(op == "~" && isInputRange!R)
898         {
899             shouldThrow!UnitTestException(shouldBeSameSetAs(forward!value, range), file, line);
900         }
901 
902         void opBinary(string op, E)
903                      (in E expected, string file = __FILE__, size_t line = __LINE__)
904             if (isFloatingPoint!E)
905         {
906             shouldThrow!UnitTestException(shouldApproxEqual(forward!value, expected), file, line);
907         }
908     }
909 
910     struct Should {
911 
912         bool opEquals(U)(auto ref U other,
913                          in string file = __FILE__,
914                          in size_t line = __LINE__)
915         {
916             shouldEqual(forward!value, other, file, line);
917             return true;
918         }
919 
920         void opBinary(string op, R)(R range,
921                                     in string file = __FILE__,
922                                     in size_t line = __LINE__) const
923             if(op == "in")
924         {
925             shouldBeIn(forward!value, range, file, line);
926         }
927 
928         void opBinary(string op, R)(R range,
929                                     in string file = __FILE__,
930                                     in size_t line = __LINE__) const
931             if(op == "~" && isInputRange!R)
932         {
933             shouldBeSameSetAs(forward!value, range, file, line);
934         }
935 
936         void opBinary(string op, E)
937                      (in E expected, string file = __FILE__, size_t line = __LINE__)
938             if (isFloatingPoint!E)
939         {
940             shouldApproxEqual(forward!value, expected, 1e-2, 1e-5, file, line);
941         }
942 
943         auto not() {
944             return ShouldNot();
945         }
946     }
947 
948     return Should();
949 }
950 
951 
952 
953 T be(T)(T sh) {
954     return sh;
955 }
956 
957 ///
958 @safe pure unittest {
959     1.should.be == 1;
960     1.should.not.be == 2;
961     1.should.be in [1, 2, 3];
962     4.should.not.be in [1, 2, 3];
963 }