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