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 }