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