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