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 package void utFail(in string output, in string file, in size_t line)
516 {
517     fail(output, file, line);
518 }
519 
520 void fail(in string output, in string file, in size_t line)
521 {
522     throw new UnitTestException([output], file, line);
523 }
524 
525 
526 private string[] formatValue(T)(in string prefix, T value) {
527     static if(isSomeString!T) {
528         return [ prefix ~ `"` ~ value ~ `"`];
529     } else static if(isInputRange!T) {
530         return formatRange(prefix, value);
531     } else {
532         return [() @trusted{ return prefix ~ value.to!string; }()];
533     }
534 }
535 
536 private string[] formatRange(T)(in string prefix, T value) @trusted {
537     //some versions of `to` are @system
538     auto defaultLines = () @trusted{ return [prefix ~ value.to!string]; }();
539 
540     static if (!isInputRange!(ElementType!T))
541         return defaultLines;
542     else
543     {
544         const maxElementSize = value.empty ? 0 : value.map!(a => a.length).reduce!max;
545         const tooBigForOneLine = (value.length > 5 && maxElementSize > 5) || maxElementSize > 10;
546         if (!tooBigForOneLine)
547             return defaultLines;
548         return [prefix ~ "["] ~
549             value.map!(a => formatValue("              ", a).join("") ~ ",").array ~
550             "          ]";
551     }
552 }
553 
554 private enum isObject(T) = is(T == class) || is(T == interface);
555 
556 private bool isEqual(V, E)(in V value, in E expected)
557  if (!isObject!V &&
558      (!isInputRange!V || !isInputRange!E) &&
559      !isFloatingPoint!V && !isFloatingPoint!E &&
560      is(typeof(value == expected) == bool))
561 {
562     return value == expected;
563 }
564 
565 private bool isEqual(V, E)(in V value, in E expected)
566  if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool))
567 {
568     import std.math;
569     return approxEqual(value, expected);
570 }
571 
572 private bool isEqual(V, E)(V value, E expected)
573 if (!isObject!V && isInputRange!V && isInputRange!E && is(typeof(value.front == expected.front) == bool))
574 {
575     return equal(value, expected);
576 }
577 
578 private bool isEqual(V, E)(V value, E expected)
579 if (!isObject!V &&
580     isInputRange!V && isInputRange!E && !is(typeof(value.front == expected.front) == bool) &&
581     isInputRange!(ElementType!V) && isInputRange!(ElementType!E))
582 {
583     while (!value.empty && !expected.empty)
584     {
585         if (!equal(value.front, expected.front))
586             return false;
587 
588         value.popFront;
589         expected.popFront;
590     }
591 
592     return value.empty && expected.empty;
593 }
594 
595 private bool isEqual(V, E)(V value, E expected)
596 if (isObject!V && isObject!E)
597 {
598     static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})),
599                   "Cannot compare instances of " ~ V.stringof ~
600                   " or " ~ E.stringof ~ " unless toString is overridden for both");
601 
602     return value.tupleof == expected.tupleof;
603 }
604 
605 
606 unittest {
607     assert(isEqual(2, 2));
608     assert(!isEqual(2, 3));
609 
610     assert(isEqual(2.1, 2.1));
611     assert(!isEqual(2.1, 2.2));
612 
613     assert(isEqual("foo", "foo"));
614     assert(!isEqual("foo", "fooo"));
615 
616     assert(isEqual([1, 2], [1, 2]));
617     assert(!isEqual([1, 2], [1, 2, 3]));
618 
619     assert(isEqual(iota(2), [0, 1]));
620     assert(!isEqual(iota(2), [1, 2, 3]));
621 
622     assert(isEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]));
623     assert(isEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]));
624     assert(!isEqual([[0, 1], [0, 1, 4]], [iota(2), iota(3)]));
625     assert(!isEqual([[0, 1], [0]], [iota(2), iota(3)]));
626 
627     assert(isEqual([0: 1], [0: 1]));
628 
629     const constIntToInts = [1 : 2, 3 : 7, 9 : 345];
630     auto intToInts = [1 : 2, 3 : 7, 9 : 345];
631 
632     assert(isEqual(intToInts, constIntToInts));
633     assert(isEqual(constIntToInts, intToInts));
634 
635     class Foo
636     {
637         this(int i) { this.i = i; }
638         override string toString() const { return i.to!string; }
639         int i;
640     }
641 
642     assert(isEqual(new Foo(5), new Foo(5)));
643     assert(!isEqual(new Foo(5), new Foo(4)));
644 
645     ubyte[] arr;
646     assert(isEqual(arr, []));
647 }
648 
649 
650 private void assertFail(E)(lazy E expression)
651 {
652     assertThrown!UnitTestException(expression);
653 }
654 
655 /**
656  * Verify that rng is empty.
657  * Throws: UnitTestException on failure.
658  */
659 void shouldBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
660 if (isInputRange!R)
661 {
662     if (!rng.empty)
663         fail("Range not empty", file, line);
664 }
665 
666 /**
667  * Verify that aa is empty.
668  * Throws: UnitTestException on failure.
669  */
670 void shouldBeEmpty(T)(in T aa, in string file = __FILE__, in size_t line = __LINE__)
671 if (isAssociativeArray!T)
672 {
673     //keys is @system
674     () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }();
675 }
676 
677 ///
678 unittest
679 {
680     int[] ints;
681     string[] strings;
682     string[string] aa;
683 
684     shouldBeEmpty(ints);
685     shouldBeEmpty(strings);
686     shouldBeEmpty(aa);
687 
688     ints ~= 1;
689     strings ~= "foo";
690     aa["foo"] = "bar";
691 
692     assertFail(shouldBeEmpty(ints));
693     assertFail(shouldBeEmpty(strings));
694     assertFail(shouldBeEmpty(aa));
695 }
696 
697 
698 /**
699  * Verify that rng is not empty.
700  * Throws: UnitTestException on failure.
701  */
702 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__)
703 if (isInputRange!R)
704 {
705     if (rng.empty)
706         fail("Range empty", file, line);
707 }
708 
709 /**
710  * Verify that aa is not empty.
711  * Throws: UnitTestException on failure.
712  */
713 void shouldNotBeEmpty(T)(in T aa, in string file = __FILE__, in size_t line = __LINE__)
714 if (isAssociativeArray!T)
715 {
716     //keys is @system
717     () @trusted{ if (aa.keys.empty)
718         fail("AA empty", file, line); }();
719 }
720 
721 ///
722 unittest
723 {
724     int[] ints;
725     string[] strings;
726     string[string] aa;
727 
728     assertFail(shouldNotBeEmpty(ints));
729     assertFail(shouldNotBeEmpty(strings));
730     assertFail(shouldNotBeEmpty(aa));
731 
732     ints ~= 1;
733     strings ~= "foo";
734     aa["foo"] = "bar";
735 
736     shouldNotBeEmpty(ints);
737     shouldNotBeEmpty(strings);
738     shouldNotBeEmpty(aa);
739 }
740 
741 /**
742  * Verify that t is greater than u.
743  * Throws: UnitTestException on failure.
744  */
745 void shouldBeGreaterThan(T, U)(in T t, in U u,
746                                in string file = __FILE__, in size_t line = __LINE__)
747 {
748     if (t <= u)
749         fail(text(t, " is not > ", u), file, line);
750 }
751 
752 ///
753 unittest
754 {
755     shouldBeGreaterThan(7, 5);
756     assertFail(shouldBeGreaterThan(5, 7));
757     assertFail(shouldBeGreaterThan(7, 7));
758 }
759 
760 
761 /**
762  * Verify that t is smaller than u.
763  * Throws: UnitTestException on failure.
764  */
765 void shouldBeSmallerThan(T, U)(in T t, in U u,
766                                in string file = __FILE__, in size_t line = __LINE__)
767 {
768     if (t >= u)
769         fail(text(t, " is not < ", u), file, line);
770 }
771 
772 ///
773 unittest
774 {
775     shouldBeSmallerThan(5, 7);
776     assertFail(shouldBeSmallerThan(7, 5));
777     assertFail(shouldBeSmallerThan(7, 7));
778 }
779 
780 
781 
782 /**
783  * Verify that t and u represent the same set (ordering is not important).
784  * Throws: UnitTestException on failure.
785  */
786 void shouldBeSameSetAs(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
787 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
788 {
789     if (!isSameSet(value, expected))
790     {
791         const msg = formatValue("Expected: ", expected) ~
792                     formatValue("     Got: ", value);
793         throw new UnitTestException(msg, file, line);
794     }
795 }
796 
797 ///
798 unittest
799 {
800     auto inOrder = iota(4);
801     auto noOrder = [2, 3, 0, 1];
802     auto oops = [2, 3, 4, 5];
803 
804     inOrder.shouldBeSameSetAs(noOrder);
805     inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException;
806 
807     struct Struct
808     {
809         int i;
810     }
811 
812     [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]);
813 }
814 
815 private bool isSameSet(T, U)(T t, U u) {
816     //sort makes the element types have to implement opCmp
817     //instead, try one by one
818     auto ta = t.array;
819     auto ua = u.array;
820     if (ta.length != ua.length) return false;
821     foreach(element; ta)
822     {
823         if (!ua.canFind(element)) return false;
824     }
825 
826     return true;
827 }
828 
829 /**
830  * Verify that value and expected do not represent the same set (ordering is not important).
831  * Throws: UnitTestException on failure.
832  */
833 void shouldNotBeSameSetAs(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__)
834 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool))
835 {
836     if (isSameSet(value, expected))
837     {
838         const msg = ["Value:",
839                      formatValue("", value).join(""),
840                      "is not expected to be equal to:",
841                      formatValue("", expected).join("")
842             ];
843         throw new UnitTestException(msg, file, line);
844     }
845 }
846 
847 
848 ///
849 unittest
850 {
851     auto inOrder = iota(4);
852     auto noOrder = [2, 3, 0, 1];
853     auto oops = [2, 3, 4, 5];
854 
855     inOrder.shouldNotBeSameSetAs(oops);
856     inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException;
857 }