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         return value.opEquals(expected);
592     } else {
593 
594         template IsFieldOf(T, string s) {
595             static if(__traits(compiles, IsField!(typeof(__traits(getMember, T.init, s)))))
596                 enum IsFieldOf = IsField!(typeof(__traits(getMember, T.init, s)));
597             else
598                 enum IsFieldOf = false;
599         }
600 
601         auto members(T)(T obj) {
602             import std.typecons: Tuple;
603 
604             alias Member(string name) = typeof(__traits(getMember, T, name));
605             alias IsFieldOfT(string s) = IsFieldOf!(T, s);
606             alias FieldNames = Filter!(IsFieldOfT, __traits(allMembers, T));
607             alias FieldTypes = staticMap!(Member, FieldNames);
608 
609             Tuple!FieldTypes ret;
610             foreach(i, name; FieldNames)
611                 ret[i] = __traits(getMember, obj, name);
612 
613             return ret;
614         }
615 
616         static if(is(V == interface))
617             return false;
618         else
619             return members(value) == members(expected);
620     }
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(V)(scope auto ref V value){
874 
875     import std.functional: forward;
876 
877     struct ShouldNot {
878 
879         bool opEquals(U)(auto ref U other,
880                          in string file = __FILE__,
881                          in size_t line = __LINE__)
882         {
883             shouldNotEqual(forward!value, other, file, line);
884             return true;
885         }
886 
887         void opBinary(string op, R)(R range,
888                                     in string file = __FILE__,
889                                     in size_t line = __LINE__) const if(op == "in") {
890             shouldNotBeIn(forward!value, range, file, line);
891         }
892 
893         void opBinary(string op, R)(R range,
894                                     in string file = __FILE__,
895                                     in size_t line = __LINE__) const
896             if(op == "~" && isInputRange!R)
897         {
898             shouldThrow!UnitTestException(shouldBeSameSetAs(forward!value, range), file, line);
899         }
900 
901         void opBinary(string op, E)
902                      (in E expected, string file = __FILE__, size_t line = __LINE__)
903             if (isFloatingPoint!E)
904         {
905             shouldThrow!UnitTestException(shouldApproxEqual(forward!value, expected), file, line);
906         }
907     }
908 
909     struct Should {
910 
911         bool opEquals(U)(auto ref U other,
912                          in string file = __FILE__,
913                          in size_t line = __LINE__)
914         {
915             shouldEqual(forward!value, other, file, line);
916             return true;
917         }
918 
919         void opBinary(string op, R)(R range,
920                                     in string file = __FILE__,
921                                     in size_t line = __LINE__) const
922             if(op == "in")
923         {
924             shouldBeIn(forward!value, range, file, line);
925         }
926 
927         void opBinary(string op, R)(R range,
928                                     in string file = __FILE__,
929                                     in size_t line = __LINE__) const
930             if(op == "~" && isInputRange!R)
931         {
932             shouldBeSameSetAs(forward!value, range, file, line);
933         }
934 
935         void opBinary(string op, E)
936                      (in E expected, string file = __FILE__, size_t line = __LINE__)
937             if (isFloatingPoint!E)
938         {
939             shouldApproxEqual(forward!value, expected, 1e-2, 1e-5, file, line);
940         }
941 
942         auto not() {
943             return ShouldNot();
944         }
945     }
946 
947     return Should();
948 }
949 
950 
951 
952 T be(T)(T sh) {
953     return sh;
954 }
955 
956 ///
957 @safe pure unittest {
958     1.should.be == 1;
959     1.should.not.be == 2;
960     1.should.be in [1, 2, 3];
961     4.should.not.be in [1, 2, 3];
962 }
963 
964 
965 /**
966    Asserts that `lowerBound` <= `actual` < `upperBound`
967  */
968 void shouldBeBetween(A, L, U)
969     (auto ref A actual,
970      auto ref L lowerBound,
971      auto ref U upperBound,
972      in string file = __FILE__,
973      in size_t line = __LINE__)
974 {
975     import std.conv: text;
976     if(actual < lowerBound || actual >= upperBound)
977         fail(text(actual, " is not between ", lowerBound, " and ", upperBound), file, line);
978 }