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