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