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.traits; // too many to list 10 import std.range; // also 11 12 13 /** 14 * An exception to signal that a test case has failed. 15 */ 16 class UnitTestException : Exception 17 { 18 this(in string msg, string file = __FILE__, 19 size_t line = __LINE__, Throwable next = null) @safe pure nothrow 20 { 21 this([msg], file, line, next); 22 } 23 24 this(in string[] msgLines, string file = __FILE__, 25 size_t line = __LINE__, Throwable next = null) @safe pure nothrow 26 { 27 import std.string: join; 28 super(msgLines.join("\n"), next, file, line); 29 this.msgLines = msgLines; 30 } 31 32 override string toString() @safe const pure 33 { 34 import std.algorithm: map; 35 return () @trusted { return msgLines.map!(a => getOutputPrefix(file, line) ~ a).join("\n"); }(); 36 } 37 38 private: 39 40 const string[] msgLines; 41 42 string getOutputPrefix(in string file, in size_t line) @safe const pure 43 { 44 import std.conv: to; 45 return " " ~ file ~ ":" ~ line.to!string ~ " - "; 46 } 47 } 48 49 /** 50 * Verify that the condition is `true`. 51 * Throws: UnitTestException on failure. 52 */ 53 void shouldBeTrue(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) 54 { 55 shouldEqual(cast(bool)condition, true, file, line); 56 } 57 58 /// 59 @safe pure unittest 60 { 61 shouldBeTrue(true); 62 } 63 64 65 /** 66 * Verify that the condition is `false`. 67 * Throws: UnitTestException on failure. 68 */ 69 void shouldBeFalse(E)(lazy E condition, in string file = __FILE__, in size_t line = __LINE__) 70 { 71 shouldEqual(cast(bool)condition, false, file, line); 72 } 73 74 /// 75 @safe pure unittest 76 { 77 shouldBeFalse(false); 78 } 79 80 81 /** 82 * Verify that two values are the same. 83 * Floating point values are compared using $(D std.math.approxEqual). 84 * Throws: UnitTestException on failure 85 */ 86 void shouldEqual(V, E) 87 (scope auto ref V value, scope auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) 88 { 89 if (!isEqual(value, expected)) 90 { 91 const msg = formatValueInItsOwnLine("Expected: ", expected) ~ 92 formatValueInItsOwnLine(" Got: ", value); 93 throw new UnitTestException(msg, file, line); 94 } 95 } 96 97 /// 98 @safe pure unittest { 99 shouldEqual(true, true); 100 shouldEqual(false, false); 101 shouldEqual(1, 1) ; 102 shouldEqual("foo", "foo") ; 103 shouldEqual([2, 3], [2, 3]) ; 104 105 shouldEqual(iota(3), [0, 1, 2]); 106 shouldEqual([[0, 1], [0, 1, 2]], [[0, 1], [0, 1, 2]]); 107 shouldEqual([[0, 1], [0, 1, 2]], [iota(2), iota(3)]); 108 shouldEqual([iota(2), iota(3)], [[0, 1], [0, 1, 2]]); 109 110 } 111 112 113 /** 114 * Verify that two values are not the same. 115 * Throws: UnitTestException on failure 116 */ 117 void shouldNotEqual(V, E)(V value, E expected, in string file = __FILE__, in size_t line = __LINE__) 118 { 119 if (isEqual(value, expected)) 120 { 121 const msg = ["Value:", 122 formatValueInItsOwnLine("", value).join(""), 123 "is not expected to be equal to:", 124 formatValueInItsOwnLine("", expected).join("") 125 ]; 126 throw new UnitTestException(msg, file, line); 127 } 128 } 129 130 /// 131 @safe pure unittest 132 { 133 shouldNotEqual(true, false); 134 shouldNotEqual(1, 2); 135 shouldNotEqual("f", "b"); 136 shouldNotEqual([2, 3], [2, 3, 4]); 137 } 138 139 /// 140 @safe unittest { 141 shouldNotEqual(1.0, 2.0); 142 } 143 144 145 146 /** 147 * Verify that the value is null. 148 * Throws: UnitTestException on failure 149 */ 150 void shouldBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) 151 { 152 if (value !is null) 153 fail("Value is not null", file, line); 154 } 155 156 /// 157 @safe pure unittest 158 { 159 shouldBeNull(null); 160 } 161 162 163 /** 164 * Verify that the value is not null. 165 * Throws: UnitTestException on failure 166 */ 167 void shouldNotBeNull(T)(in auto ref T value, in string file = __FILE__, in size_t line = __LINE__) 168 { 169 if (value is null) 170 fail("Value is null", file, line); 171 } 172 173 /// 174 @safe pure unittest 175 { 176 class Foo 177 { 178 this(int i) { this.i = i; } 179 override string toString() const 180 { 181 import std.conv: to; 182 return i.to!string; 183 } 184 int i; 185 } 186 187 shouldNotBeNull(new Foo(4)); 188 } 189 190 enum isLikeAssociativeArray(T, K) = is(typeof({ 191 if(K.init in T) { } 192 if(K.init !in T) { } 193 })); 194 195 static assert(isLikeAssociativeArray!(string[string], string)); 196 static assert(!isLikeAssociativeArray!(string[string], int)); 197 198 199 /** 200 * Verify that the value is in the container. 201 * Throws: UnitTestException on failure 202 */ 203 void shouldBeIn(T, U)(in auto ref T value, in auto ref U container, in string file = __FILE__, in size_t line = __LINE__) 204 if (isLikeAssociativeArray!(U, T)) 205 { 206 import std.conv: to; 207 208 if (value !in container) 209 { 210 fail(formatValueInItsOwnLine("Value ", value) ~ formatValueInItsOwnLine("not in ", container), 211 file, line); 212 } 213 } 214 215 /// 216 @safe pure unittest { 217 5.shouldBeIn([5: "foo"]); 218 219 struct AA { 220 int onlyKey; 221 bool opBinaryRight(string op)(in int key) const { 222 return key == onlyKey; 223 } 224 } 225 226 5.shouldBeIn(AA(5)); 227 } 228 229 /** 230 * Verify that the value is in the container. 231 * Throws: UnitTestException on failure 232 */ 233 void shouldBeIn(T, U)(in auto ref T value, U container, in string file = __FILE__, in size_t line = __LINE__) 234 if (!isLikeAssociativeArray!(U, T) && isInputRange!U) 235 { 236 import std.algorithm: find; 237 import std.conv: to; 238 239 if (find(container, value).empty) 240 { 241 fail(formatValueInItsOwnLine("Value ", value) ~ formatValueInItsOwnLine("not in ", container), 242 file, line); 243 } 244 } 245 246 /// 247 @safe pure unittest 248 { 249 shouldBeIn(4, [1, 2, 4]); 250 shouldBeIn("foo", ["foo" : 1]); 251 } 252 253 254 /** 255 * Verify that the value is not in the container. 256 * Throws: UnitTestException on failure 257 */ 258 void shouldNotBeIn(T, U)(in auto ref T value, in auto ref U container, 259 in string file = __FILE__, in size_t line = __LINE__) 260 if (isLikeAssociativeArray!(U, T)) 261 { 262 import std.conv: to; 263 264 if (value in container) 265 { 266 fail(formatValueInItsOwnLine("Value ", value) ~ formatValueInItsOwnLine("is in ", container), 267 file, line); 268 } 269 } 270 271 /// 272 @safe pure unittest { 273 5.shouldNotBeIn([4: "foo"]); 274 275 struct AA { 276 int onlyKey; 277 bool opBinaryRight(string op)(in int key) const { 278 return key == onlyKey; 279 } 280 } 281 282 5.shouldNotBeIn(AA(4)); 283 } 284 285 286 /** 287 * Verify that the value is not in the container. 288 * Throws: UnitTestException on failure 289 */ 290 void shouldNotBeIn(T, U)(in auto ref T value, U container, 291 in string file = __FILE__, in size_t line = __LINE__) 292 if (!isLikeAssociativeArray!(U, T) && isInputRange!U) 293 { 294 import std.algorithm: find; 295 import std.conv: to; 296 297 if (!find(container, value).empty) 298 { 299 fail(formatValueInItsOwnLine("Value ", value) ~ formatValueInItsOwnLine("is in ", container), 300 file, line); 301 } 302 } 303 304 /// 305 @safe unittest 306 { 307 auto arrayRangeWithoutLength(T)(T[] array) 308 { 309 struct ArrayRangeWithoutLength(T) 310 { 311 private: 312 T[] array; 313 public: 314 T front() const @property 315 { 316 return array[0]; 317 } 318 319 void popFront() 320 { 321 array = array[1 .. $]; 322 } 323 324 bool empty() const @property 325 { 326 return array.empty; 327 } 328 } 329 return ArrayRangeWithoutLength!T(array); 330 } 331 332 shouldNotBeIn(3.5, [1.1, 2.2, 4.4]); 333 shouldNotBeIn(1.0, [2.0 : 1, 3.0 : 2]); 334 shouldNotBeIn(1, arrayRangeWithoutLength([2, 3, 4])); 335 } 336 337 /** 338 * Verify that expr throws the templated Exception class. 339 * This succeeds if the expression throws a child class of 340 * the template parameter. 341 * Returns: The caught throwable. 342 * Throws: UnitTestException on failure (when expr does not 343 * throw the expected exception) 344 */ 345 auto shouldThrow(T : Throwable = Exception, E) 346 (lazy E expr, in string file = __FILE__, in size_t line = __LINE__) 347 { 348 import std.conv: text; 349 350 return () @trusted { // @trusted because of catching Throwable 351 try { 352 const result = threw!T(expr); 353 if (result) return result.throwable; 354 } catch(Throwable t) 355 fail(text("Expression threw ", typeid(t), " instead of the expected ", T.stringof, ":\n", t.msg), file, line); 356 357 fail("Expression did not throw", file, line); 358 assert(0); 359 }(); 360 } 361 362 /// 363 @safe pure unittest { 364 void funcThrows(string msg) { throw new Exception(msg); } 365 try { 366 auto exception = funcThrows("foo bar").shouldThrow; 367 assert(exception.msg == "foo bar"); 368 } catch(Exception e) { 369 assert(false, "should not have thrown anything and threw: " ~ e.msg); 370 } 371 } 372 373 /// 374 @safe pure unittest { 375 void func() {} 376 try { 377 func.shouldThrow; 378 assert(false, "Should never get here"); 379 } catch(Exception e) 380 assert(e.msg == "Expression did not throw"); 381 } 382 383 /// 384 @safe pure unittest { 385 void funcAsserts() { assert(false, "Oh noes"); } 386 try { 387 funcAsserts.shouldThrow; 388 assert(false, "Should never get here"); 389 } catch(Exception e) 390 assert(e.msg == 391 "Expression threw core.exception.AssertError instead of the expected Exception:\nOh noes"); 392 } 393 394 395 /** 396 * Verify that expr throws the templated Exception class. 397 * This only succeeds if the expression throws an exception of 398 * the exact type of the template parameter. 399 * Returns: The caught throwable. 400 * Throws: UnitTestException on failure (when expr does not 401 * throw the expected exception) 402 */ 403 auto shouldThrowExactly(T : Throwable = Exception, E)(lazy E expr, 404 in string file = __FILE__, in size_t line = __LINE__) 405 { 406 import std.conv: text; 407 408 const threw = threw!T(expr); 409 if (!threw) 410 fail("Expression did not throw", file, line); 411 412 //Object.opEquals is @system and impure 413 const sameType = () @trusted { return threw.typeInfo == typeid(T); }(); 414 if (!sameType) 415 fail(text("Expression threw wrong type ", threw.typeInfo, 416 "instead of expected type ", typeid(T)), file, line); 417 418 return threw.throwable; 419 } 420 421 /** 422 * Verify that expr does not throw the templated Exception class. 423 * Throws: UnitTestException on failure 424 */ 425 void shouldNotThrow(T: Throwable = Exception, E)(lazy E expr, 426 in string file = __FILE__, in size_t line = __LINE__) 427 { 428 if (threw!T(expr)) 429 fail("Expression threw", file, line); 430 } 431 432 433 /** 434 * Verify that an exception is thrown with the right message 435 */ 436 void shouldThrowWithMessage(T : Throwable = Exception, E)(lazy E expr, 437 string msg, 438 string file = __FILE__, 439 size_t line = __LINE__) { 440 auto threw = threw!T(expr); 441 if (!threw) 442 fail("Expression did not throw", file, line); 443 444 threw.throwable.msg.shouldEqual(msg, file, line); 445 } 446 447 /// 448 @safe pure unittest { 449 void funcThrows(string msg) { throw new Exception(msg); } 450 funcThrows("foo bar").shouldThrowWithMessage!Exception("foo bar"); 451 funcThrows("foo bar").shouldThrowWithMessage("foo bar"); 452 } 453 454 455 //@trusted because the user might want to catch a throwable 456 //that's not derived from Exception, such as RangeError 457 private auto threw(T : Throwable, E)(lazy E expr) @trusted 458 { 459 460 static struct ThrowResult 461 { 462 bool threw; 463 TypeInfo typeInfo; 464 immutable(T) throwable; 465 466 T opCast(T)() @safe @nogc const pure if (is(T == bool)) 467 { 468 return threw; 469 } 470 } 471 472 import std.stdio; 473 try 474 { 475 expr(); 476 } 477 catch (T e) 478 { 479 return ThrowResult(true, typeid(e), cast(immutable)e); 480 } 481 482 return ThrowResult(false); 483 } 484 485 486 487 void fail(in string output, in string file, in size_t line) @safe pure 488 { 489 throw new UnitTestException([output], file, line); 490 } 491 492 void fail(in string[] lines, in string file, in size_t line) @safe pure 493 { 494 throw new UnitTestException(lines, file, line); 495 } 496 497 498 // Formats output in different lines 499 private string[] formatValueInItsOwnLine(T)(in string prefix, scope auto ref T value) { 500 501 import std.conv: to; 502 503 static if(isSomeString!T) { 504 // isSomeString is true for wstring and dstring, 505 // so call .to!string anyway 506 return [ prefix ~ `"` ~ value.to!string ~ `"`]; 507 } else static if(isInputRange!T) { 508 return formatRange(prefix, value); 509 } else { 510 return [prefix ~ convertToString(value)]; 511 } 512 } 513 514 // helper function for non-copyable types 515 string convertToString(T)(scope auto ref T value) { // std.conv.to sometimes is @system 516 import std.conv: to; 517 import std.traits: Unqual; 518 519 static if(__traits(compiles, value.to!string)) 520 return () @trusted { return value.to!string; }(); 521 else static if(__traits(compiles, value.toString)) { 522 static if(isObject!T) 523 return () @trusted { return (cast(Unqual!T)value).toString; }(); 524 else 525 return value.toString; 526 } else 527 return T.stringof ~ "<cannot print>"; 528 } 529 530 531 private string[] formatRange(T)(in string prefix, scope auto ref T value) { 532 import std.conv: to; 533 import std.range: ElementType; 534 import std.algorithm: map, reduce, max; 535 536 //some versions of `to` are @system 537 auto defaultLines = () @trusted { return [prefix ~ value.to!string]; }(); 538 539 static if (!isInputRange!(ElementType!T)) 540 return defaultLines; 541 else 542 { 543 import std.array: array; 544 const maxElementSize = value.empty ? 0 : value.map!(a => a.array.length).reduce!max; 545 const tooBigForOneLine = (value.array.length > 5 && maxElementSize > 5) || maxElementSize > 10; 546 if (!tooBigForOneLine) 547 return defaultLines; 548 return [prefix ~ "["] ~ 549 value.map!(a => formatValueInItsOwnLine(" ", a).join("") ~ ",").array ~ 550 " ]"; 551 } 552 } 553 554 private enum isObject(T) = is(T == class) || is(T == interface); 555 556 bool isEqual(V, E)(in auto ref V value, in auto ref E expected) 557 if (!isObject!V && 558 !isFloatingPoint!V && !isFloatingPoint!E && 559 is(typeof(value == expected) == bool)) 560 { 561 return value == expected; 562 } 563 564 bool isEqual(V, E)(in V value, in E expected) 565 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool)) 566 { 567 return value == expected; 568 } 569 570 571 bool isApproxEqual(V, E)(in V value, in E expected) 572 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool)) 573 { 574 import std.math; 575 return approxEqual(value, expected); 576 } 577 578 579 void shouldApproxEqual(V, E)(in V value, in E expected, string file = __FILE__, size_t line = __LINE__) 580 if (!isObject!V && (isFloatingPoint!V || isFloatingPoint!E) && is(typeof(value == expected) == bool)) 581 { 582 if (!isApproxEqual(value, expected)) 583 { 584 const msg = 585 formatValueInItsOwnLine("Expected approx: ", expected) ~ 586 formatValueInItsOwnLine(" Got : ", value); 587 throw new UnitTestException(msg, file, line); 588 } 589 } 590 591 /// 592 @safe unittest { 593 1.0.shouldApproxEqual(1.0001); 594 } 595 596 597 bool isEqual(V, E)(V value, E expected) 598 if (!isObject!V && isInputRange!V && isInputRange!E && !isSomeString!V && 599 is(typeof(isEqual(value.front, expected.front)))) 600 { 601 602 while (!value.empty && !expected.empty) { 603 if(!isEqual(value.front, expected.front)) return false; 604 value.popFront; 605 expected.popFront; 606 } 607 608 return value.empty && expected.empty; 609 } 610 611 bool isEqual(V, E)(V value, E expected) 612 if (!isObject!V && isInputRange!V && isInputRange!E && isSomeString!V && isSomeString!E && 613 is(typeof(isEqual(value.front, expected.front)))) 614 { 615 if(value.length != expected.length) return false; 616 // prevent auto-decoding 617 foreach(i; 0 .. value.length) 618 if(value[i] != expected[i]) return false; 619 620 return true; 621 } 622 623 template IsField(A...) if(A.length == 1) { 624 enum IsField = __traits(compiles, A[0].init); 625 } 626 627 628 bool isEqual(V, E)(V value, E expected) 629 if (isObject!V && isObject!E) 630 { 631 import std.meta: staticMap, Filter; 632 633 static assert(is(typeof(() { string s1 = value.toString; string s2 = expected.toString;})), 634 "Cannot compare instances of " ~ V.stringof ~ 635 " or " ~ E.stringof ~ " unless toString is overridden for both"); 636 637 if(value is null && expected !is null) return false; 638 if(value !is null && expected is null) return false; 639 if(value is null && expected is null) return true; 640 641 template IsFieldOf(T, string s) { 642 static if(__traits(compiles, IsField!(typeof(__traits(getMember, T.init, s))))) 643 enum IsFieldOf = IsField!(typeof(__traits(getMember, T.init, s))); 644 else 645 enum IsFieldOf = false; 646 } 647 648 auto members(T)(T obj) { 649 import std.typecons: Tuple; 650 651 alias Member(string name) = typeof(__traits(getMember, T, name)); 652 alias IsFieldOfT(string s) = IsFieldOf!(T, s); 653 alias FieldNames = Filter!(IsFieldOfT, __traits(allMembers, T)); 654 alias FieldTypes = staticMap!(Member, FieldNames); 655 656 Tuple!FieldTypes ret; 657 foreach(i, name; FieldNames) 658 ret[i] = __traits(getMember, obj, name); 659 660 return ret; 661 } 662 663 static if(is(V == interface)) 664 return false; 665 else 666 return members(value) == members(expected); 667 } 668 669 670 /** 671 * Verify that rng is empty. 672 * Throws: UnitTestException on failure. 673 */ 674 void shouldBeEmpty(R)(in auto ref R rng, in string file = __FILE__, in size_t line = __LINE__) 675 if (isInputRange!R) 676 { 677 import std.conv: text; 678 if (!rng.empty) 679 fail(text("Range not empty: ", rng), file, line); 680 } 681 682 /** 683 * Verify that rng is empty. 684 * Throws: UnitTestException on failure. 685 */ 686 void shouldBeEmpty(R)(auto ref shared(R) rng, in string file = __FILE__, in size_t line = __LINE__) 687 if (isInputRange!R) 688 { 689 import std.conv: text; 690 if (!rng.empty) 691 fail(text("Range not empty: ", rng), file, line); 692 } 693 694 695 /** 696 * Verify that aa is empty. 697 * Throws: UnitTestException on failure. 698 */ 699 void shouldBeEmpty(T)(auto ref T aa, in string file = __FILE__, in size_t line = __LINE__) 700 if (isAssociativeArray!T) 701 { 702 //keys is @system 703 () @trusted{ if (!aa.keys.empty) fail("AA not empty", file, line); }(); 704 } 705 706 /// 707 @safe pure unittest 708 { 709 int[] ints; 710 string[] strings; 711 string[string] aa; 712 713 shouldBeEmpty(ints); 714 shouldBeEmpty(strings); 715 shouldBeEmpty(aa); 716 717 ints ~= 1; 718 strings ~= "foo"; 719 aa["foo"] = "bar"; 720 } 721 722 723 /** 724 * Verify that rng is not empty. 725 * Throws: UnitTestException on failure. 726 */ 727 void shouldNotBeEmpty(R)(R rng, in string file = __FILE__, in size_t line = __LINE__) 728 if (isInputRange!R) 729 { 730 if (rng.empty) 731 fail("Range empty", file, line); 732 } 733 734 /** 735 * Verify that aa is not empty. 736 * Throws: UnitTestException on failure. 737 */ 738 void shouldNotBeEmpty(T)(in auto ref T aa, in string file = __FILE__, in size_t line = __LINE__) 739 if (isAssociativeArray!T) 740 { 741 //keys is @system 742 () @trusted{ if (aa.keys.empty) 743 fail("AA empty", file, line); }(); 744 } 745 746 /// 747 @safe pure unittest 748 { 749 int[] ints; 750 string[] strings; 751 string[string] aa; 752 753 ints ~= 1; 754 strings ~= "foo"; 755 aa["foo"] = "bar"; 756 757 shouldNotBeEmpty(ints); 758 shouldNotBeEmpty(strings); 759 shouldNotBeEmpty(aa); 760 } 761 762 /** 763 * Verify that t is greater than u. 764 * Throws: UnitTestException on failure. 765 */ 766 void shouldBeGreaterThan(T, U)(in auto ref T t, in auto ref U u, 767 in string file = __FILE__, in size_t line = __LINE__) 768 { 769 import std.conv: text; 770 if (t <= u) 771 fail(text(t, " is not > ", u), file, line); 772 } 773 774 /// 775 @safe pure unittest 776 { 777 shouldBeGreaterThan(7, 5); 778 } 779 780 781 /** 782 * Verify that t is smaller than u. 783 * Throws: UnitTestException on failure. 784 */ 785 void shouldBeSmallerThan(T, U)(in auto ref T t, in auto ref U u, 786 in string file = __FILE__, in size_t line = __LINE__) 787 { 788 import std.conv: text; 789 if (t >= u) 790 fail(text(t, " is not < ", u), file, line); 791 } 792 793 /// 794 @safe pure unittest 795 { 796 shouldBeSmallerThan(5, 7); 797 } 798 799 800 801 /** 802 * Verify that t and u represent the same set (ordering is not important). 803 * Throws: UnitTestException on failure. 804 */ 805 void shouldBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) 806 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) 807 { 808 if (!isSameSet(value, expected)) 809 { 810 const msg = formatValueInItsOwnLine("Expected: ", expected) ~ 811 formatValueInItsOwnLine(" Got: ", value); 812 throw new UnitTestException(msg, file, line); 813 } 814 } 815 816 /// 817 @safe pure unittest 818 { 819 import std.range: iota; 820 821 auto inOrder = iota(4); 822 auto noOrder = [2, 3, 0, 1]; 823 auto oops = [2, 3, 4, 5]; 824 825 inOrder.shouldBeSameSetAs(noOrder); 826 inOrder.shouldBeSameSetAs(oops).shouldThrow!UnitTestException; 827 828 struct Struct 829 { 830 int i; 831 } 832 833 [Struct(1), Struct(4)].shouldBeSameSetAs([Struct(4), Struct(1)]); 834 } 835 836 private bool isSameSet(T, U)(auto ref T t, auto ref U u) { 837 import std.algorithm: canFind; 838 839 //sort makes the element types have to implement opCmp 840 //instead, try one by one 841 auto ta = t.array; 842 auto ua = u.array; 843 if (ta.length != ua.length) return false; 844 foreach(element; ta) 845 { 846 if (!ua.canFind(element)) return false; 847 } 848 849 return true; 850 } 851 852 /** 853 * Verify that value and expected do not represent the same set (ordering is not important). 854 * Throws: UnitTestException on failure. 855 */ 856 void shouldNotBeSameSetAs(V, E)(auto ref V value, auto ref E expected, in string file = __FILE__, in size_t line = __LINE__) 857 if (isInputRange!V && isInputRange!E && is(typeof(value.front != expected.front) == bool)) 858 { 859 if (isSameSet(value, expected)) 860 { 861 const msg = ["Value:", 862 formatValueInItsOwnLine("", value).join(""), 863 "is not expected to be equal to:", 864 formatValueInItsOwnLine("", expected).join("") 865 ]; 866 throw new UnitTestException(msg, file, line); 867 } 868 } 869 870 871 /// 872 @safe pure unittest 873 { 874 auto inOrder = iota(4); 875 auto noOrder = [2, 3, 0, 1]; 876 auto oops = [2, 3, 4, 5]; 877 878 inOrder.shouldNotBeSameSetAs(oops); 879 inOrder.shouldNotBeSameSetAs(noOrder).shouldThrow!UnitTestException; 880 } 881 882 883 884 885 /** 886 If two strings represent the same JSON regardless of formatting 887 */ 888 void shouldBeSameJsonAs(in string actual, 889 in string expected, 890 in string file = __FILE__, 891 in size_t line = __LINE__) 892 @trusted // not @safe pure due to parseJSON 893 { 894 import std.json: parseJSON, JSONException; 895 896 auto parse(in string str) { 897 try 898 return str.parseJSON; 899 catch(JSONException ex) 900 throw new UnitTestException("Error parsing JSON: " ~ ex.msg, file, line); 901 } 902 903 parse(actual).toPrettyString.shouldEqual(parse(expected).toPrettyString, file, line); 904 } 905 906 /// 907 @safe unittest { // not pure because parseJSON isn't pure 908 `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo": "bar"}`); 909 `{"foo": "bar"}`.shouldBeSameJsonAs(`{"foo":"bar"}`); 910 `{"foo":"bar"}`.shouldBeSameJsonAs(`{"foo": "baz"}`).shouldThrow!UnitTestException; 911 try 912 `oops`.shouldBeSameJsonAs(`oops`); 913 catch(Exception e) 914 assert(e.msg == "Error parsing JSON: Unexpected character 'o'. (Line 1:1)"); 915 } 916 917 918 919 auto should(E)(lazy E expr) { 920 921 struct ShouldNot { 922 923 bool opEquals(U)(auto ref U other, 924 in string file = __FILE__, 925 in size_t line = __LINE__) 926 { 927 expr.shouldNotEqual(other, file, line); 928 return true; 929 } 930 931 void opBinary(string op, R)(R range, 932 in string file = __FILE__, 933 in size_t line = __LINE__) const if(op == "in") { 934 shouldNotBeIn(expr, range, file, line); 935 } 936 937 void opBinary(string op, R)(R range, 938 in string file = __FILE__, 939 in size_t line = __LINE__) const 940 if(op == "~" && isInputRange!R) 941 { 942 shouldThrow!UnitTestException(shouldBeSameSetAs(expr, range), file, line); 943 } 944 945 void opBinary(string op, E) 946 (in E expected, string file = __FILE__, size_t line = __LINE__) 947 if (isFloatingPoint!E) 948 { 949 shouldThrow!UnitTestException(shouldApproxEqual(expr, expected), file, line); 950 } 951 952 // void opDispatch(string s, A...)(auto ref A args) 953 // { 954 // import std.functional: forward; 955 // mixin(`Should().` ~ string ~ `(forward!args)`); 956 // } 957 } 958 959 struct Should { 960 961 bool opEquals(U)(auto ref U other, 962 in string file = __FILE__, 963 in size_t line = __LINE__) 964 { 965 expr.shouldEqual(other, file, line); 966 return true; 967 } 968 969 void throw_(T : Throwable = Exception) 970 (in string file = __FILE__, in size_t line = __LINE__) 971 { 972 shouldThrow!T(expr, file, line); 973 } 974 975 void throwExactly(T : Throwable = Exception) 976 (in string file = __FILE__, in size_t line = __LINE__) 977 { 978 shouldThrowExactly!T(expr, file, line); 979 } 980 981 void throwWithMessage(T : Throwable = Exception) 982 (in string file = __FILE__, in size_t line = __LINE__) 983 { 984 shouldThrowWithMessage!T(expr, file, line); 985 } 986 987 void opBinary(string op, R)(R range, 988 in string file = __FILE__, 989 in size_t line = __LINE__) const 990 if(op == "in") 991 { 992 shouldBeIn(expr, range, file, line); 993 } 994 995 void opBinary(string op, R)(R range, 996 in string file = __FILE__, 997 in size_t line = __LINE__) const 998 if(op == "~" && isInputRange!R) 999 { 1000 shouldBeSameSetAs(expr, range, file, line); 1001 } 1002 1003 void opBinary(string op, E) 1004 (in E expected, string file = __FILE__, size_t line = __LINE__) 1005 if (isFloatingPoint!E) 1006 { 1007 shouldApproxEqual(expr, expected, file, line); 1008 } 1009 1010 auto not() { 1011 return ShouldNot(); 1012 } 1013 } 1014 1015 return Should(); 1016 } 1017 1018 /// 1019 @safe pure unittest { 1020 1.should == 1; 1021 1.should.not == 2; 1022 1.should in [1, 2, 3]; 1023 4.should.not in [1, 2, 3]; 1024 1025 void funcThrows() { throw new Exception("oops"); } 1026 funcThrows.should.throw_; 1027 } 1028 1029 T be(T)(T sh) { 1030 return sh; 1031 } 1032 1033 /// 1034 @safe pure unittest { 1035 1.should.be == 1; 1036 1.should.not.be == 2; 1037 1.should.be in [1, 2, 3]; 1038 4.should.not.be in [1, 2, 3]; 1039 }