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 /** 400 * Verify that an exception is thrown with the right message 401 */ 402 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, 403 string msg, 404 string file = __FILE__, 405 size_t line = __LINE__) { 406 auto threw = threw!T(expr); 407 if (!threw) 408 fail("Expression did not throw", file, line); 409 410 threw.throwable.msg.shouldEqual(msg); 411 } 412 413 /// 414 unittest { 415 void funcThrows(string msg) { throw new Exception(msg); } 416 funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar"); 417 funcThrows("foo bar").shouldThrowWithMessage("foo bar"); 418 assertFail(funcThrows("boo boo").shouldThrowWithMessage("foo bar")); 419 } 420 421 422 //@trusted because the user might want to catch a throwable 423 //that's not derived from Exception, such as RangeError 424 private auto threw(T : Throwable, E)(lazy E expr) @trusted 425 { 426 427 struct ThrowResult 428 { 429 bool threw; 430 TypeInfo typeInfo; 431 immutable(T) throwable; 432 433 T opCast(T)() const pure if (is(T == bool)) 434 { 435 return threw; 436 } 437 } 438 439 try 440 { 441 expr(); 442 } 443 catch (T e) 444 { 445 return ThrowResult(true, typeid(e), cast(immutable)e); 446 } 447 448 return ThrowResult(false); 449 } 450 451 unittest 452 { 453 class CustomException : Exception 454 { 455 this(string msg = "") 456 { 457 super(msg); 458 } 459 } 460 461 class ChildException : CustomException 462 { 463 this(string msg = "") 464 { 465 super(msg); 466 } 467 } 468 469 void throwCustom() 470 { 471 throw new CustomException(); 472 } 473 474 throwCustom.shouldThrow; 475 throwCustom.shouldThrow!CustomException; 476 477 void throwChild() 478 { 479 throw new ChildException(); 480 } 481 482 throwChild.shouldThrow; 483 throwChild.shouldThrow!CustomException; 484 throwChild.shouldThrow!ChildException; 485 throwChild.shouldThrowExactly!ChildException; 486 try 487 { 488 throwChild.shouldThrowExactly!CustomException; //should not succeed 489 assert(0, "shouldThrowExactly failed"); 490 } 491 catch (Exception ex) 492 { 493 } 494 } 495 496 unittest 497 { 498 void throwRangeError() 499 { 500 ubyte[] bytes; 501 bytes = bytes[1 .. $]; 502 } 503 504 import core.exception : RangeError; 505 506 throwRangeError.shouldThrow!RangeError; 507 } 508 509 package void utFail(in string output, in string file, in size_t line) 510 { 511 fail(output, file, line); 512 } 513 514 private void fail(in string output, in string file, in size_t line) 515 { 516 throw new UnitTestException([output], file, line); 517 } 518 519 520 private string[] formatValue(T)(in string prefix, T value) { 521 static if(isSomeString!T) { 522 return [ prefix ~ `"` ~ value ~ `"`]; 523 } else static if(isInputRange!T) { 524 return formatRange(prefix, value); 525 } else { 526 return [() @trusted{ return prefix ~ value.to!string; }()]; 527 } 528 } 529 530 private string[] formatRange(T)(in string prefix, T value) @trusted { 531 //some versions of `to` are @system 532 auto defaultLines = () @trusted{ return [prefix ~ value.to!string]; }(); 533 534 static if (!isInputRange!(ElementType!T)) 535 return defaultLines; 536 else 537 { 538 const maxElementSize = value.empty ? 0 : value.map!(a => a.length).reduce!max; 539 const tooBigForOneLine = (value.length > 5 && maxElementSize > 5) || maxElementSize > 10; 540 if (!tooBigForOneLine) 541 return defaultLines; 542 return [prefix ~ "["] ~ 543 value.map!(a => formatValue(" ", a).join("") ~ ",").array ~ 544 " ]"; 545 } 546 } 547 548 private enum isObject(T) = is(T == class) || is(T == interface); 549 550 private bool isEqual(V, E)(in V value, in E expected) 551 if (!isObject!V && 552 (!isInputRange!V || !isInputRange!E) && 553 !isFloatingPoint!V && !isFloatingPoint!E && 554 is(typeof(value == expected) == bool)) 555 { 556 return value == expected; 557 } 558 559 private bool isEqual(V, E)(in V value, in E expected) 560 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool)) 561 { 562 import std.math; 563 return approxEqual(value, expected); 564 } 565 566 private bool isEqual(V, E)(V value, E expected) 567 if (!isObject!V && isInputRange!V && isInputRange!E && is(typeof(value.front == expected.front) == bool)) 568 { 569 return equal(value, expected); 570 } 571 572 private bool isEqual(V, E)(V value, E expected) 573 if (!isObject!V && 574 isInputRange!V && isInputRange!E && !is(typeof(value.front == expected.front) == bool) && 575 isInputRange!(ElementType!V) && isInputRange!(ElementType!E)) 576 { 577 while (!value.empty && !expected.empty) 578 { 579 if (!equal(value.front, expected.front)) 580 return false; 581 582 value.popFront; 583 expected.popFront; 584 } 585 586 return value.empty && expected.empty; 587 } 588 589 private bool isEqual(V, E)(V value, E expected) 590 if (isObject!V && isObject!E) 591 { 592 static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})), 593 "Cannot compare instances of " ~ V.stringof ~ 594 " or " ~ E.stringof ~ " unless toString is overridden for both"); 595 596 return value.tupleof == expected.tupleof; 597 } 598 599 600 unittest { 601 assert(isEqual(2, 2)); 602 assert(!isEqual(2, 3)); 603 604 assert(isEqual(2.1, 2.1)); 605 assert(!isEqual(2.1, 2.2)); 606 607 assert(isEqual("foo", "foo")); 608 assert(!isEqual("foo", "fooo")); 609 610 assert(isEqual([1, 2], [1, 2])); 611 assert(!isEqual([1, 2], [1, 2, 3])); 612 613 assert(isEqual(iota(2), [0, 1])); 614 assert(!isEqual(iota(2), [1, 2, 3])); 615 616 assert(isEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)])); 617 assert(isEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]])); 618 assert(!isEqual([[0, 1], [0, 1, 4]], [iota(2), iota(3)])); 619 assert(!isEqual([[0, 1], [0]], [iota(2), iota(3)])); 620 621 assert(isEqual([0: 1], [0: 1])); 622 623 const constIntToInts = [1 : 2, 3 : 7, 9 : 345]; 624 auto intToInts = [1 : 2, 3 : 7, 9 : 345]; 625 626 assert(isEqual(intToInts, constIntToInts)); 627 assert(isEqual(constIntToInts, intToInts)); 628 629 class Foo 630 { 631 this(int i) { this.i = i; } 632 override string toString() const { return i.to!string; } 633 int i; 634 } 635 636 assert(isEqual(new Foo(5), new Foo(5))); 637 assert(!isEqual(new Foo(5), new Foo(4))); 638 639 ubyte[] arr; 640 assert(isEqual(arr, [])); 641 } 642 643 644 private void assertFail(E)(lazy E expression) 645 { 646 assertThrown!UnitTestException(expression); 647 } 648 649 /** 650 * Verify that rng is empty. 651 * Throws: UnitTestException on failure. 652 */ 653 void shouldBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__) 654 if (isInputRange!R) 655 { 656 if (!rng.empty) 657 fail("Range not empty", file, line); 658 } 659 660 /** 661 * Verify that aa is empty. 662 * Throws: UnitTestException on failure. 663 */ 664 void shouldBeEmpty(T)(in T aa, in string file = __FILE__, in size_t line = __LINE__) 665 if (isAssociativeArray!T) 666 { 667 //keys is @system 668 () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }(); 669 } 670 671 /// 672 unittest 673 { 674 int[] ints; 675 string[] strings; 676 string[string] aa; 677 678 shouldBeEmpty(ints); 679 shouldBeEmpty(strings); 680 shouldBeEmpty(aa); 681 682 ints ~= 1; 683 strings ~= "foo"; 684 aa["foo"] = "bar"; 685 686 assertFail(shouldBeEmpty(ints)); 687 assertFail(shouldBeEmpty(strings)); 688 assertFail(shouldBeEmpty(aa)); 689 } 690 691 692 /** 693 * Verify that rng is not empty. 694 * Throws: UnitTestException on failure. 695 */ 696 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__) 697 if (isInputRange!R) 698 { 699 if (rng.empty) 700 fail("Range empty", file, line); 701 } 702 703 /** 704 * Verify that aa is not empty. 705 * Throws: UnitTestException on failure. 706 */ 707 void shouldNotBeEmpty(T)(in T aa, in string file = __FILE__, in size_t line = __LINE__) 708 if (isAssociativeArray!T) 709 { 710 //keys is @system 711 () @trusted{ if (aa.keys.empty) 712 fail("AA empty", file, line); }(); 713 } 714 715 /// 716 unittest 717 { 718 int[] ints; 719 string[] strings; 720 string[string] aa; 721 722 assertFail(shouldNotBeEmpty(ints)); 723 assertFail(shouldNotBeEmpty(strings)); 724 assertFail(shouldNotBeEmpty(aa)); 725 726 ints ~= 1; 727 strings ~= "foo"; 728 aa["foo"] = "bar"; 729 730 shouldNotBeEmpty(ints); 731 shouldNotBeEmpty(strings); 732 shouldNotBeEmpty(aa); 733 } 734 735 /** 736 * Verify that t is greater than u. 737 * Throws: UnitTestException on failure. 738 */ 739 void shouldBeGreaterThan(T, U)(in T t, in U u, 740 in string file = __FILE__, in size_t line = __LINE__) 741 { 742 if (t <= u) 743 fail(text(t, " is not > ", u), file, line); 744 } 745 746 /// 747 unittest 748 { 749 shouldBeGreaterThan(7, 5); 750 assertFail(shouldBeGreaterThan(5, 7)); 751 assertFail(shouldBeGreaterThan(7, 7)); 752 } 753 754 755 /** 756 * Verify that t is smaller than u. 757 * Throws: UnitTestException on failure. 758 */ 759 void shouldBeSmallerThan(T, U)(in T t, in U u, 760 in string file = __FILE__, in size_t line = __LINE__) 761 { 762 if (t >= u) 763 fail(text(t, " is not < ", u), file, line); 764 } 765 766 /// 767 unittest 768 { 769 shouldBeSmallerThan(5, 7); 770 assertFail(shouldBeSmallerThan(7, 5)); 771 assertFail(shouldBeSmallerThan(7, 7)); 772 } 773 774 775 776 /** 777 * Verify that t and u represent the same set (ordering is not important). 778 * Throws: UnitTestException on failure. 779 */ 780 void shouldBeSameSetAs(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__) 781 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) 782 { 783 if (!isSameSet(value, expected)) 784 { 785 const msg = formatValue("Expected: ", expected) ~ 786 formatValue(" Got: ", value); 787 throw new UnitTestException(msg, file, line); 788 } 789 } 790 791 /// 792 unittest 793 { 794 auto inOrder = iota(4); 795 auto noOrder = [2, 3, 0, 1]; 796 auto oops = [2, 3, 4, 5]; 797 798 inOrder.shouldBeSameSetAs(noOrder); 799 inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException; 800 801 struct Struct 802 { 803 int i; 804 } 805 806 [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]); 807 } 808 809 private bool isSameSet(T, U)(T t, U u) { 810 //sort makes the element types have to implement opCmp 811 //instead, try one by one 812 auto ta = t.array; 813 auto ua = u.array; 814 if (ta.length != ua.length) return false; 815 foreach(element; ta) 816 { 817 if (!ua.canFind(element)) return false; 818 } 819 820 return true; 821 } 822 823 /** 824 * Verify that value and expected do not represent the same set (ordering is not important). 825 * Throws: UnitTestException on failure. 826 */ 827 void shouldNotBeSameSetAs(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__) 828 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) 829 { 830 if (isSameSet(value, expected)) 831 { 832 const msg = ["Value:", 833 formatValue("", value).join(""), 834 "is not expected to be equal to:", 835 formatValue("", expected).join("") 836 ]; 837 throw new UnitTestException(msg, file, line); 838 } 839 } 840 841 842 /// 843 unittest 844 { 845 auto inOrder = iota(4); 846 auto noOrder = [2, 3, 0, 1]; 847 auto oops = [2, 3, 4, 5]; 848 849 inOrder.shouldNotBeSameSetAs(oops); 850 inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException; 851 }