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