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