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