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