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 }