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