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