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.exception;
10 import std.conv;
11 import std.algorithm;
12 import std.traits;
13 import std.range;
14 
15 public import unit_threaded.attrs;
16 
17 @safe:
18 
19 /**
20  * An exception to signal that a test case has failed.
21  */
22 class UnitTestException : Exception
23 {
24     this(in string[] msgLines, string file = __FILE__,
25          size_t line = __LINE__, Throwable next = null)
26     {
27         super(msgLines.join("\n"), next, file, line);
28         this.msgLines = msgLines;
29     }
30 
31     override string toString() const pure
32     {
33         return msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n");
34     }
35 
36 private:
37 
38     const string[] msgLines;
39 
40     string getOutputPrefix(in string file, in size_t line) const pure
41     {
42         return "    " ~ file ~ ":" ~ line.to!string ~ " - ";
43     }
44 }
45 
46 /**
47  * Verify that the condition is `true`.
48  * Throws: UnitTestException on failure.
49  */
50 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__)
51 {
52     shouldEqual(condition, true, file, line);
53 }
54 
55 ///
56 unittest
57 {
58     shouldBeTrue(true);
59 }
60 
61 /**
62  * Verify that the condition is `false`.
63  * Throws: UnitTestException on failure.
64  */
65 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__)
66 {
67     shouldEqual(condition, false, file, line);
68 }
69 
70 ///
71 unittest
72 {
73     shouldBeFalse(false);
74 }
75 
76 /**
77  * Verify that two values are the same.
78  * Floating point values are compared using $(D std.math.approxEqual).
79  * Throws: UnitTestException on failure
80  */
81 void shouldEqual(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
82 {
83     if (!isEqual(value, expected))
84     {
85         const msg = formatValue("Expected: ", expected) ~
86                     formatValue("     Got: ", value);
87         throw new UnitTestException(msg, file, line);
88     }
89 }
90 
91 ///
92 unittest {
93     shouldEqual(true, true);
94     shouldEqual(false, false);
95     shouldEqual(1, 1) ;
96     shouldEqual("foo", "foo") ;
97     shouldEqual(1.0, 1.0) ;
98     shouldEqual([2, 3], [2, 3]) ;
99 
100     shouldEqual(iota(3), [0, 1, 2]);
101     shouldEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]);
102     shouldEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]);
103     shouldEqual([iota(2), iota(3)], [[0, 1], [0, 1, 2]]);
104 
105     shouldEqual(3.0, 3.00001); //approximately equal
106 }
107 
108 /**
109  * Verify that two values are not the same.
110  * Throws: UnitTestException on failure
111  */
112 void shouldNotEqual(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
113 {
114     if (isEqual(value, expected))
115     {
116         const msg = ["Value:",
117                      formatValue("", value).join(""),
118                      "is not expected to be equal to:",
119                      formatValue("", expected).join("")
120             ];
121         throw new UnitTestException(msg, file, line);
122     }
123 }
124 
125 ///
126 unittest
127 {
128     shouldNotEqual(true, false);
129     shouldNotEqual(1, 2);
130     shouldNotEqual("f", "b");
131     shouldNotEqual(1.0, 2.0);
132     shouldNotEqual([2, 3], [2, 3, 4]);
133 }
134 
135 
136 unittest {
137     string getExceptionMsg(E)(lazy E expr) {
138         try
139         {
140             expr();
141         }
142         catch(UnitTestException ex)
143         {
144             return ex.toString;
145         }
146         assert(0, "Expression did not throw UnitTestException");
147     }
148 
149     void assertExceptionMsg(E)(lazy E expr, string expected,
150                                in size_t line = __LINE__)
151     {
152         import std.string: stripLeft;
153         import std.path: dirSeparator;
154 
155         //updating the tests below as line numbers change is tedious.
156         //instead, replace the number there with the actual line number
157         expected = expected.replace(":123", ":" ~ line.to!string).replace("/", dirSeparator);
158         auto msg = getExceptionMsg(expr);
159         auto expLines = expected.split("\n").map!stripLeft;
160         auto msgLines = msg.split("\n").map!stripLeft;
161         assert(zip(msgLines, expLines).all!(a => a[0].endsWith(a[1])),
162                text("\nExpected Exception:\n", expected, "\nGot Exception:\n", msg));
163     }
164 
165     assertExceptionMsg(3.shouldEqual(5),
166                        "    source/unit_threaded/should.d:123 - Expected: 5\n"
167                        "    source/unit_threaded/should.d:123 -      Got: 3");
168 
169     assertExceptionMsg("foo".shouldEqual("bar"),
170                        "    source/unit_threaded/should.d:123 - Expected: \"bar\"\n"
171                        "    source/unit_threaded/should.d:123 -      Got: \"foo\"");
172 
173     assertExceptionMsg([1, 2, 4].shouldEqual([1, 2, 3]),
174                        "    source/unit_threaded/should.d:123 - Expected: [1, 2, 3]\n"
175                        "    source/unit_threaded/should.d:123 -      Got: [1, 2, 4]");
176 
177     assertExceptionMsg([[0, 1, 2, 3, 4], [1], [2], [3], [4], [5]].shouldEqual([[0], [1], [2]]),
178                        "    source/unit_threaded/should.d:123 - Expected: [[0], [1], [2]]\n"
179                        "    source/unit_threaded/should.d:123 -      Got: [[0, 1, 2, 3, 4], [1], [2], [3], [4], [5]]");
180 
181     assertExceptionMsg([[0, 1, 2, 3, 4, 5], [1], [2], [3]].shouldEqual([[0], [1], [2]]),
182                        "    source/unit_threaded/should.d:123 - Expected: [[0], [1], [2]]\n"
183                        "    source/unit_threaded/should.d:123 -      Got: [[0, 1, 2, 3, 4, 5], [1], [2], [3]]");
184 
185 
186     assertExceptionMsg([[0, 1, 2, 3, 4, 5], [1], [2], [3], [4], [5]].shouldEqual([[0]]),
187                        "    source/unit_threaded/should.d:123 - Expected: [[0]]\n"
188 
189                        "    source/unit_threaded/should.d:123 -      Got: [\n"
190                        "    source/unit_threaded/should.d:123 -               [0, 1, 2, 3, 4, 5],\n"
191                        "    source/unit_threaded/should.d:123 -               [1],\n"
192                        "    source/unit_threaded/should.d:123 -               [2],\n"
193                        "    source/unit_threaded/should.d:123 -               [3],\n"
194                        "    source/unit_threaded/should.d:123 -               [4],\n"
195                        "    source/unit_threaded/should.d:123 -               [5],\n"
196                        "    source/unit_threaded/should.d:123 -           ]");
197 
198     assertExceptionMsg(1.shouldNotEqual(1),
199                        "    source/unit_threaded/should.d:123 - Value:\n"
200                        "    source/unit_threaded/should.d:123 - 1\n"
201                        "    source/unit_threaded/should.d:123 - is not expected to be equal to:\n"
202                        "    source/unit_threaded/should.d:123 - 1");
203 }
204 
205 unittest
206 {
207     ubyte[] arr;
208     arr.shouldEqual([]);
209 }
210 
211 
212 unittest
213 {
214     int[] ints = [1, 2, 3];
215     byte[] bytes = [1, 2, 3];
216     byte[] bytes2 = [1, 2, 4];
217     shouldEqual(ints, bytes);
218     shouldEqual(bytes, ints) ;
219     shouldNotEqual(ints, bytes2) ;
220 
221     shouldEqual([1 : 2.0, 2 : 4.0], [1 : 2.0, 2 : 4.0]) ;
222     shouldNotEqual([1 : 2.0, 2 : 4.0], [1 : 2.2, 2 : 4.0]) ;
223     const constIntToInts = [1 : 2, 3 : 7, 9 : 345];
224     auto intToInts = [1 : 2, 3 : 7, 9 : 345];
225     shouldEqual(intToInts, constIntToInts) ;
226     shouldEqual(constIntToInts, intToInts) ;
227 }
228 
229 /**
230  * Verify that the value is null.
231  * Throws: UnitTestException on failure
232  */
233 void shouldBeNull(T)(in T value, in string file = __FILE__, in size_t line = __LINE__)
234 {
235     if (value !is null)
236         fail("Value is null", file, line);
237 }
238 
239 ///
240 unittest
241 {
242     shouldBeNull(null) ;
243 }
244 
245 
246 /**
247  * Verify that the value is not null.
248  * Throws: UnitTestException on failure
249  */
250 void shouldNotBeNull(T)(in T value, in string file = __FILE__, in size_t line = __LINE__)
251 {
252     if (value is null)
253         fail("Value is null", file, line);
254 }
255 
256 ///
257 unittest
258 {
259     class Foo
260     {
261         this(int i) { this.i = i; }
262         override string toString() const
263         {
264             import std.conv: to;
265             return i.to!string;
266         }
267         int i;
268     }
269 
270     shouldNotBeNull(new Foo(4)) ;
271     shouldEqual(new Foo(5), new Foo(5));
272     assertFail(shouldEqual(new Foo(5), new Foo(4)));
273     shouldNotEqual(new Foo(5), new Foo(4)) ;
274     assertFail(shouldNotEqual(new Foo(5), new Foo(5)));
275 }
276 
277 
278 /**
279  * Verify that the value is in the container.
280  * Throws: UnitTestException on failure
281 */
282 void shouldBeIn(T, U)(in T value, in U container, in string file = __FILE__, in size_t line = __LINE__)
283 if (isAssociativeArray!U)
284 {
285     if (value !in container)
286     {
287         fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file,
288             line);
289     }
290 }
291 
292 /**
293  * Verify that the value is in the container.
294  * Throws: UnitTestException on failure
295  */
296 void shouldBeIn(T, U)(in T value, U container, in string file = __FILE__, in size_t line = __LINE__)
297 if (!isAssociativeArray!U && isInputRange!U)
298 {
299     if (find(container, value).empty)
300     {
301         fail("Value " ~ to!string(value) ~ " not in " ~ to!string(container), file,
302             line);
303     }
304 }
305 
306 ///
307 unittest
308 {
309     shouldBeIn(4, [1, 2, 4]);
310     shouldBeIn("foo", ["foo" : 1]);
311 }
312 
313 
314 /**
315  * Verify that the value is not in the container.
316  * Throws: UnitTestException on failure
317  */
318 void shouldNotBeIn(T, U)(in T value, in U container,
319                          in string file = __FILE__, in size_t line = __LINE__)
320 if (isAssociativeArray!U)
321 {
322     if (value in container)
323     {
324         fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file,
325             line);
326     }
327 }
328 
329 
330 /**
331  * Verify that the value is not in the container.
332  * Throws: UnitTestException on failure
333  */
334 void shouldNotBeIn(T, U)(in T value, U container,
335                          in string file = __FILE__, in size_t line = __LINE__)
336 if (!isAssociativeArray!U && isInputRange!U)
337 {
338     if (find(container, value).length > 0)
339     {
340         fail("Value " ~ to!string(value) ~ " is in " ~ to!string(container), file,
341             line);
342     }
343 }
344 
345 ///
346 unittest
347 {
348     shouldNotBeIn(3.5, [1.1, 2.2, 4.4]);
349     shouldNotBeIn(1.0, [2.0 : 1, 3.0 : 2]);
350 }
351 
352 /**
353  * Verify that expr throws the templated Exception class.
354  * This succeeds if the expression throws a child class of
355  * the template parameter.
356  * Throws: UnitTestException on failure (when expr does not
357  * throw the expected exception)
358  */
359 void shouldThrow(T : Throwable = Exception, E)(lazy E expr,
360     in string file = __FILE__, in size_t line = __LINE__)
361 {
362     if (!threw!T(expr))
363         fail("Expression did not throw", file, line);
364 }
365 
366 /**
367  * Verify that expr throws the templated Exception class.
368  * This only succeeds if the expression throws an exception of
369  * the exact type of the template parameter.
370  * Throws: UnitTestException on failure (when expr does not
371  * throw the expected exception)
372  */
373 void shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr,
374     in string file = __FILE__, in size_t line = __LINE__)
375 {
376 
377     immutable threw = threw!T(expr);
378     if (!threw)
379         fail("Expression did not throw", file, line);
380 
381     //Object.opEquals is @system
382     immutable sameType = () @trusted{ return threw.typeInfo == typeid(T); }();
383     if (!sameType)
384         fail(text("Expression threw wrong type ", threw.typeInfo,
385             "instead of expected type ", typeid(T)), file, line);
386 }
387 
388 /**
389  * Verify that expr does not throw the templated Exception class.
390  * Throws: UnitTestException on failure
391  */
392 void shouldNotThrow(T : Throwable = Exception, E)(lazy E expr,
393     in string file = __FILE__, in size_t line = __LINE__)
394 {
395     if (threw!T(expr))
396         fail("Expression threw", file, line);
397 }
398 
399 /**
400  * Verify that an exception is thrown with the right message
401  */
402 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr,
403                                                           string msg,
404                                                           string file = __FILE__,
405                                                           size_t line = __LINE__) {
406     auto threw = threw!T(expr);
407     if (!threw)
408         fail("Expression did not throw", file, line);
409 
410     threw.throwable.msg.shouldEqual(msg);
411 }
412 
413 ///
414 unittest {
415     void funcThrows(string msg) { throw new Exception(msg); }
416     funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar");
417     funcThrows("foo bar").shouldThrowWithMessage("foo bar");
418     assertFail(funcThrows("boo boo").shouldThrowWithMessage("foo bar"));
419 }
420 
421 
422 //@trusted because the user might want to catch a throwable
423 //that's not derived from Exception, such as RangeError
424 private auto threw(T : Throwable, E)(lazy E expr) @trusted
425 {
426 
427     struct ThrowResult
428     {
429         bool threw;
430         TypeInfo typeInfo;
431         immutable(T) throwable;
432 
433         T opCast(T)() const pure if (is(T == bool))
434         {
435             return threw;
436         }
437     }
438 
439     try
440     {
441         expr();
442     }
443     catch (T e)
444     {
445         return ThrowResult(true, typeid(e), cast(immutable)e);
446     }
447 
448     return ThrowResult(false);
449 }
450 
451 unittest
452 {
453     class CustomException : Exception
454     {
455         this(string msg = "")
456         {
457             super(msg);
458         }
459     }
460 
461     class ChildException : CustomException
462     {
463         this(string msg = "")
464         {
465             super(msg);
466         }
467     }
468 
469     void throwCustom()
470     {
471         throw new CustomException();
472     }
473 
474     throwCustom.shouldThrow;
475     throwCustom.shouldThrow!CustomException;
476 
477     void throwChild()
478     {
479         throw new ChildException();
480     }
481 
482     throwChild.shouldThrow;
483     throwChild.shouldThrow!CustomException;
484     throwChild.shouldThrow!ChildException;
485     throwChild.shouldThrowExactly!ChildException;
486     try
487     {
488         throwChild.shouldThrowExactly!CustomException; //should not succeed
489         assert(0, "shouldThrowExactly failed");
490     }
491     catch (Exception ex)
492     {
493     }
494 }
495 
496 unittest
497 {
498     void throwRangeError()
499     {
500         ubyte[] bytes;
501         bytes = bytes[1 .. $];
502     }
503 
504     import core.exception : RangeError;
505 
506     throwRangeError.shouldThrow!RangeError;
507 }
508 
509 package void utFail(in string output, in string file, in size_t line)
510 {
511     fail(output, file, line);
512 }
513 
514 private void fail(in string output, in string file, in size_t line)
515 {
516     throw new UnitTestException([output], file, line);
517 }
518 
519 
520 private string[] formatValue(T)(in string prefix, T value) {
521     static if(isSomeString!T) {
522         return [ prefix ~ `"` ~ value ~ `"`];
523     } else static if(isInputRange!T) {
524         return formatRange(prefix, value);
525     } else {
526         return [() @trusted{ return prefix ~ value.to!string; }()];
527     }
528 }
529 
530 private string[] formatRange(T)(in string prefix, T value) @trusted {
531     //some versions of `to` are @system
532     auto defaultLines = () @trusted{ return [prefix ~ value.to!string]; }();
533 
534     static if (!isInputRange!(ElementType!T))
535         return defaultLines;
536     else
537     {
538         const maxElementSize = value.empty ? 0 : value.map!(a => a.length).reduce!max;
539         const tooBigForOneLine = (value.length > 5 && maxElementSize > 5) || maxElementSize > 10;
540         if (!tooBigForOneLine)
541             return defaultLines;
542         return [prefix ~ "["] ~
543             value.map!(a => formatValue("              ", a).join("") ~ ",").array ~
544             "          ]";
545     }
546 }
547 
548 private enum isObject(T) = is(T == class) || is(T == interface);
549 
550 private bool isEqual(V, E)(in V value, in E expected)
551  if (!isObject!V &&
552      (!isInputRange!V || !isInputRange!E) &&
553      !isFloatingPoint!V && !isFloatingPoint!E &&
554      is(typeof(value == expected) == bool))
555 {
556     return value == expected;
557 }
558 
559 private bool isEqual(V, E)(in V value, in E expected)
560  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
561 {
562     import std.math;
563     return approxEqual(value, expected);
564 }
565 
566 private bool isEqual(V, E)(V value, E expected)
567 if (!isObject!V && isInputRange!V && isInputRange!E && is(typeof(value.front == expected.front) == bool))
568 {
569     return equal(value, expected);
570 }
571 
572 private bool isEqual(V, E)(V value, E expected)
573 if (!isObject!V &&
574     isInputRange!V && isInputRange!E && !is(typeof(value.front == expected.front) == bool) &&
575     isInputRange!(ElementType!V) && isInputRange!(ElementType!E))
576 {
577     while (!value.empty && !expected.empty)
578     {
579         if (!equal(value.front, expected.front))
580             return false;
581 
582         value.popFront;
583         expected.popFront;
584     }
585 
586     return value.empty && expected.empty;
587 }
588 
589 private bool isEqual(V, E)(V value, E expected)
590 if (isObject!V && isObject!E)
591 {
592     static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})),
593                   "Cannot compare instances of " ~ V.stringof ~
594                   " or " ~ E.stringof ~ " unless toString is overridden for both");
595 
596     return value.tupleof == expected.tupleof;
597 }
598 
599 
600 unittest {
601     assert(isEqual(2, 2));
602     assert(!isEqual(2, 3));
603 
604     assert(isEqual(2.1, 2.1));
605     assert(!isEqual(2.1, 2.2));
606 
607     assert(isEqual("foo", "foo"));
608     assert(!isEqual("foo", "fooo"));
609 
610     assert(isEqual([1, 2], [1, 2]));
611     assert(!isEqual([1, 2], [1, 2, 3]));
612 
613     assert(isEqual(iota(2), [0, 1]));
614     assert(!isEqual(iota(2), [1, 2, 3]));
615 
616     assert(isEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]));
617     assert(isEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]));
618     assert(!isEqual([[0, 1], [0, 1, 4]], [iota(2), iota(3)]));
619     assert(!isEqual([[0, 1], [0]], [iota(2), iota(3)]));
620 
621     assert(isEqual([0: 1], [0: 1]));
622 
623     const constIntToInts = [1 : 2, 3 : 7, 9 : 345];
624     auto intToInts = [1 : 2, 3 : 7, 9 : 345];
625 
626     assert(isEqual(intToInts, constIntToInts));
627     assert(isEqual(constIntToInts, intToInts));
628 
629     class Foo
630     {
631         this(int i) { this.i = i; }
632         override string toString() const { return i.to!string; }
633         int i;
634     }
635 
636     assert(isEqual(new Foo(5), new Foo(5)));
637     assert(!isEqual(new Foo(5), new Foo(4)));
638 
639     ubyte[] arr;
640     assert(isEqual(arr, []));
641 }
642 
643 
644 private void assertFail(E)(lazy E expression)
645 {
646     assertThrown!UnitTestException(expression);
647 }
648 
649 /**
650  * Verify that rng is empty.
651  * Throws: UnitTestException on failure.
652  */
653 void shouldBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
654 if (isInputRange!R)
655 {
656     if (!rng.empty)
657         fail("Range not empty", file, line);
658 }
659 
660 /**
661  * Verify that aa is empty.
662  * Throws: UnitTestException on failure.
663  */
664 void shouldBeEmpty(T)(in T aa, in string file = __FILE__, in size_t line = __LINE__)
665 if (isAssociativeArray!T)
666 {
667     //keys is @system
668     () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }();
669 }
670 
671 ///
672 unittest
673 {
674     int[] ints;
675     string[] strings;
676     string[string] aa;
677 
678     shouldBeEmpty(ints);
679     shouldBeEmpty(strings);
680     shouldBeEmpty(aa);
681 
682     ints ~= 1;
683     strings ~= "foo";
684     aa["foo"] = "bar";
685 
686     assertFail(shouldBeEmpty(ints));
687     assertFail(shouldBeEmpty(strings));
688     assertFail(shouldBeEmpty(aa));
689 }
690 
691 
692 /**
693  * Verify that rng is not empty.
694  * Throws: UnitTestException on failure.
695  */
696 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
697 if (isInputRange!R)
698 {
699     if (rng.empty)
700         fail("Range empty", file, line);
701 }
702 
703 /**
704  * Verify that aa is not empty.
705  * Throws: UnitTestException on failure.
706  */
707 void shouldNotBeEmpty(T)(in T aa, in string file = __FILE__, in size_t line = __LINE__)
708 if (isAssociativeArray!T)
709 {
710     //keys is @system
711     () @trusted{ if (aa.keys.empty)
712         fail("AA empty", file, line); }();
713 }
714 
715 ///
716 unittest
717 {
718     int[] ints;
719     string[] strings;
720     string[string] aa;
721 
722     assertFail(shouldNotBeEmpty(ints));
723     assertFail(shouldNotBeEmpty(strings));
724     assertFail(shouldNotBeEmpty(aa));
725 
726     ints ~= 1;
727     strings ~= "foo";
728     aa["foo"] = "bar";
729 
730     shouldNotBeEmpty(ints);
731     shouldNotBeEmpty(strings);
732     shouldNotBeEmpty(aa);
733 }
734 
735 /**
736  * Verify that t is greater than u.
737  * Throws: UnitTestException on failure.
738  */
739 void shouldBeGreaterThan(T, U)(in T t, in U u,
740                                in string file = __FILE__, in size_t line = __LINE__)
741 {
742     if (t <= u)
743         fail(text(t, " is not > ", u), file, line);
744 }
745 
746 ///
747 unittest
748 {
749     shouldBeGreaterThan(7, 5);
750     assertFail(shouldBeGreaterThan(5, 7));
751     assertFail(shouldBeGreaterThan(7, 7));
752 }
753 
754 
755 /**
756  * Verify that t is smaller than u.
757  * Throws: UnitTestException on failure.
758  */
759 void shouldBeSmallerThan(T, U)(in T t, in U u,
760                                in string file = __FILE__, in size_t line = __LINE__)
761 {
762     if (t >= u)
763         fail(text(t, " is not < ", u), file, line);
764 }
765 
766 ///
767 unittest
768 {
769     shouldBeSmallerThan(5, 7);
770     assertFail(shouldBeSmallerThan(7, 5));
771     assertFail(shouldBeSmallerThan(7, 7));
772 }
773 
774 
775 
776 /**
777  * Verify that t and u represent the same set (ordering is not important).
778  * Throws: UnitTestException on failure.
779  */
780 void shouldBeSameSetAs(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
781 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
782 {
783     if (!isSameSet(value, expected))
784     {
785         const msg = formatValue("Expected: ", expected) ~
786                     formatValue("     Got: ", value);
787         throw new UnitTestException(msg, file, line);
788     }
789 }
790 
791 ///
792 unittest
793 {
794     auto inOrder = iota(4);
795     auto noOrder = [2, 3, 0, 1];
796     auto oops = [2, 3, 4, 5];
797 
798     inOrder.shouldBeSameSetAs(noOrder);
799     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
800 
801     struct Struct
802     {
803         int i;
804     }
805 
806     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
807 }
808 
809 private bool isSameSet(T, U)(T t, U u) {
810     //sort makes the element types have to implement opCmp
811     //instead, try one by one
812     auto ta = t.array;
813     auto ua = u.array;
814     if (ta.length != ua.length) return false;
815     foreach(element; ta)
816     {
817         if (!ua.canFind(element)) return false;
818     }
819 
820     return true;
821 }
822 
823 /**
824  * Verify that value and expected do not represent the same set (ordering is not important).
825  * Throws: UnitTestException on failure.
826  */
827 void shouldNotBeSameSetAs(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
828 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
829 {
830     if (isSameSet(value, expected))
831     {
832         const msg = ["Value:",
833                      formatValue("", value).join(""),
834                      "is not expected to be equal to:",
835                      formatValue("", expected).join("")
836             ];
837         throw new UnitTestException(msg, file, line);
838     }
839 }
840 
841 
842 ///
843 unittest
844 {
845     auto inOrder = iota(4);
846     auto noOrder = [2, 3, 0, 1];
847     auto oops = [2, 3, 4, 5];
848 
849     inOrder.shouldNotBeSameSetAs(oops);
850     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
851 }