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