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