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