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