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