1 /* 2 __ 3 / _| 4 __ _ _ _ _ __ ___ _ __ __ _ | |_ ___ ___ ___ 5 / _` | | | | '__/ _ \| '__/ _` | | _/ _ \/ __/ __| 6 | (_| | |_| | | | (_) | | | (_| | | || (_) \__ \__ \ 7 \__,_|\__,_|_| \___/|_| \__,_| |_| \___/|___/___/ 8 9 Copyright (C) 2012 Juan Manuel Cabo 10 Copyright (C) 2017 Mario Kröplin 11 Copyright (C) 2019 Aurora Free Open Source Software. 12 Copyright (C) 2019 Luís Ferreira <luis@aurorafoss.org> 13 14 This file is part of the Aurora Free Open Source Software. This 15 organization promote free and open source software that you can 16 redistribute and/or modify under the terms of the GNU Lesser General 17 Public License Version 3 as published by the Free Software Foundation or 18 (at your option) any later version approved by the Aurora Free Open Source 19 Software Organization. The license is available in the package root path 20 as 'LICENSE' file. Please review the following information to ensure the 21 GNU Lesser General Public License version 3 requirements will be met: 22 https://www.gnu.org/licenses/lgpl.html . 23 24 Alternatively, this file may be used under the terms of the GNU General 25 Public License version 3 or later as published by the Free Software 26 Foundation. Please review the following information to ensure the GNU 27 General Public License requirements will be met: 28 https://www.gnu.org/licenses/gpl-3.0.html. 29 30 NOTE: All products, services or anything associated to trademarks and 31 service marks used or referenced on this file are the property of their 32 respective companies/owners or its subsidiaries. Other names and brands 33 may be claimed as the property of others. 34 35 For more info about intellectual property visit: aurorafoss.org or 36 directly send an email to: contact (at) aurorafoss.org . 37 38 This file was based on DUnit framework. 39 More about DUnit: https://github.com/linkrope/dunit 40 */ 41 42 module aurorafw.unit.assertion; 43 44 import core.thread; 45 import core.time; 46 import std.algorithm; 47 import std.array; 48 import std.conv; 49 import std.range; 50 import std..string; 51 import std.traits; 52 import std.typecons; 53 54 /** 55 * Thrown on an assertion failure. 56 */ 57 @safe pure 58 class AssertException : Exception 59 { 60 @safe pure nothrow this(string msg, 61 string file = __FILE__, 62 size_t line = __LINE__, 63 Throwable next = null) 64 { 65 super(msg.empty ? "Assertion failure" : msg, file, line, next); 66 } 67 } 68 69 /** 70 * Thrown on an assertion failure. 71 */ 72 @safe pure 73 class AssertAllException : AssertException 74 { 75 private AssertException[] exceptions; 76 77 @safe pure nothrow this(AssertException[] exceptions, 78 string file = __FILE__, 79 size_t line = __LINE__, 80 Throwable next = null) 81 { 82 string msg = heading(exceptions.length); 83 84 exceptions.each!(exception => msg ~= '\n' ~ exception.description); 85 this.exceptions = exceptions; 86 super(msg, file, line, next); 87 } 88 89 private @safe pure nothrow static string heading(size_t count) 90 { 91 if (count == 1) 92 return "1 assertion failure:"; 93 else 94 return text(count, " assertion failures:"); 95 } 96 } 97 98 @safe pure 99 unittest 100 { 101 try { 102 throw new AssertAllException([new AssertException("simple assert from unittesting")]); 103 } catch(AssertAllException e) 104 { 105 import std.algorithm.searching : startsWith; 106 assert(e.msg.startsWith("1 assertion failure:")); 107 } 108 } 109 110 /** 111 * Returns a description of the throwable. 112 */ 113 @safe pure nothrow string description(Throwable throwable) 114 { 115 with (throwable) 116 { 117 if (file.empty) 118 return text(typeid(throwable).name, ": ", msg); 119 else 120 return text(typeid(throwable).name, "@", file, "(", line, "): ", msg); 121 } 122 } 123 124 /// 125 @safe pure 126 unittest 127 { 128 assert(description(new Throwable("foobar")) 129 == "object.Throwable: foobar"); 130 131 assert(description(new Throwable("foobar", "foo.d", 42)) 132 == "object.Throwable@foo.d(42): foobar"); 133 } 134 135 /** 136 * Returns a description of the difference between the strings. 137 */ 138 string description(string expected, string actual) @safe pure 139 { 140 const MAX_LENGTH = 20; 141 const result = diff(expected, actual); 142 const oneLiner = max(result[0].length, result[1].length) <= MAX_LENGTH 143 && !result[0].canFind("\n", "\r") 144 && !result[1].canFind("\n", "\r"); 145 146 if (oneLiner) 147 return "expected: <" ~ result[0] ~ "> but was: <" ~ result[1] ~ ">"; 148 else 149 return "expected:\n" ~ result[0] ~ "\nbut was:\n" ~ result[1]; 150 } 151 152 /// 153 @safe pure unittest 154 { 155 assert(description("ab", "Ab") == "expected: <<a>b> but was: <<A>b>"); 156 assert(description("a\nb", "A\nb") == "expected:\n<a>\nb\nbut was:\n<A>\nb"); 157 } 158 159 /** 160 * Returns a pair of strings that highlight the difference between lhs and rhs. 161 */ 162 @safe pure 163 Tuple!(string, string) diff(string)(string lhs, string rhs) 164 { 165 const MAX_LENGTH = 20; 166 167 if (lhs == rhs) 168 return tuple(lhs, rhs); 169 170 auto rest = mismatch(lhs, rhs); 171 auto retroDiff = mismatch(retro(rest[0]), retro(rest[1])); 172 auto diff = tuple(retro(retroDiff[0]), retro(retroDiff[1])); 173 string prefix = lhs[0 .. $ - rest[0].length]; 174 string suffix = lhs[prefix.length + diff[0].length .. $]; 175 176 if (prefix.length > MAX_LENGTH) 177 prefix = "..." ~ prefix[$ - MAX_LENGTH .. $]; 178 if (suffix.length > MAX_LENGTH) 179 suffix = suffix[0 .. MAX_LENGTH] ~ "..."; 180 181 return tuple( 182 prefix ~ '<' ~ diff[0] ~ '>' ~ suffix, 183 prefix ~ '<' ~ diff[1] ~ '>' ~ suffix); 184 } 185 186 /// 187 @safe pure unittest 188 { 189 assert(diff("abc", "abc") == tuple("abc", "abc")); 190 // highlight difference 191 assert(diff("abc", "Abc") == tuple("<a>bc", "<A>bc")); 192 assert(diff("abc", "aBc") == tuple("a<b>c", "a<B>c")); 193 assert(diff("abc", "abC") == tuple("ab<c>", "ab<C>")); 194 assert(diff("abc", "") == tuple("<abc>", "<>")); 195 assert(diff("abc", "abbc") == tuple("ab<>c", "ab<b>c")); 196 // abbreviate long prefix or suffix 197 assert(diff("_12345678901234567890a", "_12345678901234567890A") 198 == tuple("...12345678901234567890<a>", "...12345678901234567890<A>")); 199 assert(diff("a12345678901234567890_", "A12345678901234567890_") 200 == tuple("<a>12345678901234567890...", "<A>12345678901234567890...")); 201 } 202 203 /** 204 * Asserts that a condition is true. 205 * Throws: AssertException otherwise 206 */ 207 @safe pure 208 void assertTrue(T)(T condition, lazy string msg = null, 209 string file = __FILE__, 210 size_t line = __LINE__) 211 { 212 if (cast(bool) condition) 213 return; 214 215 fail(msg, file, line); 216 } 217 218 /// 219 @safe pure unittest 220 { 221 assertTrue(true); 222 assertTrue("foo" in ["foo": "bar"]); 223 224 auto exception = expectThrows!AssertException(assertTrue(false)); 225 226 assertEquals("Assertion failure", exception.msg); 227 } 228 229 /** 230 * Asserts that a condition is false. 231 * Throws: AssertException otherwise 232 */ 233 @safe pure 234 void assertFalse(T)(T condition, lazy string msg = null, 235 string file = __FILE__, 236 size_t line = __LINE__) 237 { 238 if (!cast(bool) condition) 239 return; 240 241 fail(msg, file, line); 242 } 243 244 /// 245 @safe pure unittest 246 { 247 assertFalse(false); 248 assertFalse("foo" in ["bar": "foo"]); 249 250 auto exception = expectThrows!AssertException(assertFalse(true)); 251 252 assertEquals("Assertion failure", exception.msg); 253 } 254 255 /** 256 * Asserts that the string values are equal. 257 * Throws: AssertException otherwise 258 */ 259 @safe pure 260 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 261 string file = __FILE__, 262 size_t line = __LINE__) 263 if (isSomeString!T) 264 { 265 if (expected == actual) 266 return; 267 268 string header = (msg.empty) ? null : msg ~ "; "; 269 270 fail(header ~ description(expected.to!string, actual.to!string), 271 file, line); 272 } 273 274 /// 275 @safe pure unittest 276 { 277 assertEquals("foo", "foo"); 278 279 auto exception = expectThrows!AssertException(assertEquals("bar", "baz")); 280 281 assertEquals("expected: <ba<r>> but was: <ba<z>>", exception.msg); 282 } 283 284 /** 285 * Asserts that the floating-point values are approximately equal. 286 * Throws: AssertException otherwise 287 */ 288 @safe 289 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 290 string file = __FILE__, 291 size_t line = __LINE__) 292 if (isFloatingPoint!T || isFloatingPoint!U) 293 { 294 import std.math : approxEqual; 295 296 if (approxEqual(expected, actual)) 297 return; 298 299 string header = (msg.empty) ? null : msg ~ "; "; 300 301 fail(header ~ format("expected: <%s> but was: <%s>", expected, actual), 302 file, line); 303 } 304 305 /// 306 @safe /*pure*/ 307 unittest // format is impure for floating point values 308 { 309 assertEquals(1, 1.01); 310 311 auto exception = expectThrows!AssertException(assertEquals(1, 1.1)); 312 313 assertEquals("expected: <1> but was: <1.1>", exception.msg); 314 } 315 316 /** 317 * Asserts that the values are equal. 318 * Throws: AssertException otherwise 319 */ 320 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 321 string file = __FILE__, 322 size_t line = __LINE__) 323 if (!isSomeString!T && !isFloatingPoint!T && !isFloatingPoint!U) 324 { 325 if (expected == actual) 326 return; 327 328 string header = (msg.empty) ? null : msg ~ "; "; 329 330 fail(header ~ format("expected: <%s> but was: <%s>", expected, actual), 331 file, line); 332 } 333 334 /// 335 @safe pure 336 unittest 337 { 338 assertEquals(42, 42); 339 340 auto exception = expectThrows!AssertException(assertEquals(42, 24)); 341 342 assertEquals("expected: <42> but was: <24>", exception.msg); 343 } 344 345 /// 346 @system 347 unittest // Object.opEquals is impure 348 { 349 Object foo = new Object(); 350 Object bar = null; 351 352 assertEquals(foo, foo); 353 assertEquals(bar, bar); 354 355 auto exception = expectThrows!AssertException(assertEquals(foo, bar)); 356 357 assertEquals("expected: <object.Object> but was: <null>", exception.msg); 358 } 359 360 /** 361 * Asserts that the arrays are equal. 362 * Throws: AssertException otherwise 363 */ 364 @safe pure 365 void assertArrayEquals(T, U)(in T[] expected, in U[] actual, lazy string msg = null, 366 string file = __FILE__, 367 size_t line = __LINE__) 368 { 369 assertRangeEquals(expected, actual, 370 msg, 371 file, line); 372 } 373 374 /** 375 * Asserts that the associative arrays are equal. 376 * Throws: AssertException otherwise 377 */ 378 pure 379 void assertArrayEquals(T, U, V)(in T[V] expected, in U[V] actual, lazy string msg = null, 380 string file = __FILE__, 381 size_t line = __LINE__) 382 { 383 string header = (msg.empty) ? null : msg ~ "; "; 384 385 foreach (key; expected.byKey) 386 if (key in actual) 387 { 388 assertEquals(expected[key], actual[key], 389 format(header ~ "mismatch at key %s", key.repr), 390 file, line); 391 } 392 393 auto difference = setSymmetricDifference(expected.keys.sort(), actual.keys.sort()); 394 395 assertEmpty(difference, 396 format("key mismatch; difference: %(%s, %)", difference), 397 file, line); 398 } 399 400 /// 401 @system pure 402 unittest // keys, values, byKey, byValue not usable in @safe context 403 { 404 int[string] expected = ["foo": 1, "bar": 2]; 405 406 assertArrayEquals(expected, ["foo": 1, "bar": 2]); 407 408 AssertException exception; 409 410 exception = expectThrows!AssertException(assertArrayEquals(expected, ["foo": 2])); 411 assertEquals(`mismatch at key "foo"; expected: <1> but was: <2>`, exception.msg); 412 exception = expectThrows!AssertException(assertArrayEquals(expected, ["foo": 1])); 413 assertEquals(`key mismatch; difference: "bar"`, exception.msg); 414 } 415 416 /** 417 * Asserts that the ranges are equal. 418 * Throws: AssertException otherwise 419 */ 420 @safe pure 421 void assertRangeEquals(R1, R2)(R1 expected, R2 actual, lazy string msg = null, 422 string file = __FILE__, 423 size_t line = __LINE__) 424 if (isInputRange!R1 && isInputRange!R2 && is(typeof(expected.front == actual.front))) 425 { 426 string header = (msg.empty) ? null : msg ~ "; "; 427 size_t index = 0; 428 429 for (; !expected.empty && ! actual.empty; ++index, expected.popFront, actual.popFront) 430 { 431 assertEquals(expected.front, actual.front, 432 header ~ format("mismatch at index %s", index), 433 file, line); 434 } 435 assertEmpty(expected, 436 header ~ format("length mismatch at index %s; ", index) ~ 437 format("expected: <%s> but was: empty", expected.front), 438 file, line); 439 assertEmpty(actual, 440 header ~ format("length mismatch at index %s; ", index) ~ 441 format("expected: empty but was: <%s>", actual.front), 442 file, line); 443 } 444 445 /// 446 @safe pure 447 unittest 448 { 449 int[] expected = [0, 1]; 450 451 assertRangeEquals(expected, [0, 1]); 452 453 AssertException exception; 454 455 exception = expectThrows!AssertException(assertRangeEquals(expected, [0])); 456 assertEquals("length mismatch at index 1; expected: <1> but was: empty", exception.msg); 457 exception = expectThrows!AssertException(assertRangeEquals(expected, [0, 1, 2])); 458 assertEquals("length mismatch at index 2; expected: empty but was: <2>", exception.msg); 459 exception = expectThrows!AssertException(assertArrayEquals("bar", "baz")); 460 assertEquals("mismatch at index 2; expected: <r> but was: <z>", exception.msg); 461 } 462 463 /** 464 * Asserts that the value is empty. 465 * Throws: AssertException otherwise 466 */ 467 @safe pure 468 void assertEmpty(T)(T actual, lazy string msg = null, 469 string file = __FILE__, 470 size_t line = __LINE__) 471 { 472 if (actual.empty) 473 return; 474 475 fail(msg, file, line); 476 } 477 478 /// 479 @safe pure 480 unittest 481 { 482 assertEmpty([]); 483 484 auto exception = expectThrows!AssertException(assertEmpty([1, 2, 3])); 485 486 assertEquals("Assertion failure", exception.msg); 487 } 488 489 /** 490 * Asserts that the value is not empty. 491 * Throws: AssertException otherwise 492 */ 493 @safe pure 494 void assertNotEmpty(T)(T actual, lazy string msg = null, 495 string file = __FILE__, 496 size_t line = __LINE__) 497 { 498 if (!actual.empty) 499 return; 500 501 fail(msg, file, line); 502 } 503 504 /// 505 @safe pure 506 unittest 507 { 508 assertNotEmpty([1, 2, 3]); 509 510 auto exception = expectThrows!AssertException(assertNotEmpty([])); 511 512 assertEquals("Assertion failure", exception.msg); 513 } 514 515 /** 516 * Asserts that the value is null. 517 * Throws: AssertException otherwise 518 */ 519 @safe pure 520 void assertNull(T)(T actual, lazy string msg = null, 521 string file = __FILE__, 522 size_t line = __LINE__) 523 { 524 if (actual is null) 525 return; 526 527 fail(msg, file, line); 528 } 529 530 /// 531 @safe pure 532 unittest 533 { 534 Object foo = new Object(); 535 536 assertNull(null); 537 538 auto exception = expectThrows!AssertException(assertNull(foo)); 539 540 assertEquals("Assertion failure", exception.msg); 541 } 542 543 /** 544 * Asserts that the value is not null. 545 * Throws: AssertException otherwise 546 */ 547 @safe pure 548 void assertNotNull(T)(T actual, lazy string msg = null, 549 string file = __FILE__, 550 size_t line = __LINE__) 551 { 552 if (actual !is null) 553 return; 554 555 fail(msg, file, line); 556 } 557 558 /// 559 @safe pure 560 unittest 561 { 562 Object foo = new Object(); 563 564 assertNotNull(foo); 565 566 auto exception = expectThrows!AssertException(assertNotNull(null)); 567 568 assertEquals("Assertion failure", exception.msg); 569 } 570 571 /** 572 * Asserts that the values are the same. 573 * Throws: AssertException otherwise 574 */ 575 void assertSame(T, U)(T expected, U actual, lazy string msg = null, 576 string file = __FILE__, 577 size_t line = __LINE__) 578 { 579 if (expected is actual) 580 return; 581 582 string header = (msg.empty) ? null : msg ~ "; "; 583 584 fail(header ~ format("expected same: <%s> was not: <%s>", expected, actual), 585 file, line); 586 } 587 588 /// 589 @system 590 unittest // format is impure and not safe for Object 591 { 592 Object foo = new Object(); 593 Object bar = new Object(); 594 595 assertSame(foo, foo); 596 597 auto exception = expectThrows!AssertException(assertSame(foo, bar)); 598 599 assertEquals("expected same: <object.Object> was not: <object.Object>", exception.msg); 600 } 601 602 /** 603 * Asserts that the values are not the same. 604 * Throws: AssertException otherwise 605 */ 606 @safe pure 607 void assertNotSame(T, U)(T expected, U actual, lazy string msg = null, 608 string file = __FILE__, 609 size_t line = __LINE__) 610 { 611 if (expected !is actual) 612 return; 613 614 string header = (msg.empty) ? null : msg ~ "; "; 615 616 fail(header ~ "expected not same", 617 file, line); 618 } 619 620 /// 621 @safe pure 622 unittest 623 { 624 Object foo = new Object(); 625 Object bar = new Object(); 626 627 assertNotSame(foo, bar); 628 629 auto exception = expectThrows!AssertException(assertNotSame(foo, foo)); 630 631 assertEquals("expected not same", exception.msg); 632 } 633 634 /** 635 * Asserts that all assertions pass. 636 * Throws: AssertAllException otherwise 637 */ 638 @safe pure 639 void assertAll(void delegate() @safe pure [] assertions ...) 640 { 641 AssertException[] exceptions = null; 642 643 foreach (assertion; assertions) 644 try 645 assertion(); 646 catch (AssertException exception) 647 exceptions ~= exception; 648 if (!exceptions.empty) 649 { 650 // [Issue 16345] IFTI fails with lazy variadic function in some cases 651 const file = null; 652 const line = 0; 653 654 throw new AssertAllException(exceptions, file, line); 655 } 656 } 657 658 /// 659 @safe pure 660 unittest 661 { 662 assertAll( 663 assertTrue(true), 664 assertFalse(false), 665 ); 666 667 auto exception = expectThrows!AssertException(assertAll( 668 assertTrue(false), 669 assertFalse(true), 670 )); 671 672 assertTrue(exception.msg.canFind("2 assertion failures"), exception.msg); 673 } 674 675 676 /** 677 * Asserts that the expression throws the specified throwable. 678 * Throws: AssertException otherwise 679 * Returns: the caught throwable 680 */ 681 @safe pure 682 T expectThrows(T : Throwable = Exception, E)(lazy E expression, lazy string msg = null, 683 string file = __FILE__, 684 size_t line = __LINE__) 685 { 686 try 687 expression(); 688 catch (T throwable) 689 return throwable; 690 691 string header = (msg.empty) ? null : msg ~ "; "; 692 693 fail(header ~ format("expected <%s> was not thrown", T.stringof), 694 file, line); 695 assert(0); 696 } 697 698 /// 699 @safe pure 700 unittest 701 { 702 import std.exception : enforce; 703 704 auto exception = expectThrows(enforce(false)); 705 706 assertEquals("Enforcement failed", exception.msg); 707 } 708 709 /// 710 @safe pure 711 unittest 712 { 713 auto exception = expectThrows!AssertException(expectThrows(42)); 714 715 assertEquals("expected <Exception> was not thrown", exception.msg); 716 } 717 718 /** 719 * Fails a test. 720 * Throws: AssertException 721 */ 722 @safe pure 723 void fail(string msg = null, 724 string file = __FILE__, 725 size_t line = __LINE__) 726 { 727 throw new AssertException(msg, file, line); 728 } 729 730 /// 731 @safe pure 732 unittest 733 { 734 auto exception = expectThrows!AssertException(fail()); 735 736 assertEquals("Assertion failure", exception.msg); 737 } 738 739 alias assertGreaterThan = assertOp!">"; 740 alias assertGreaterThanOrEqual = assertOp!">="; 741 alias assertLessThan = assertOp!"<"; 742 alias assertLessThanOrEqual = assertOp!"<="; 743 alias assertIn = assertOp!"in"; 744 alias assertNotIn = assertOp!"!in"; 745 746 /** 747 * Asserts that the condition (lhs op rhs) is satisfied. 748 * Throws: AssertException otherwise 749 * See_Also: http://d.puremagic.com/issues/show_bug.cgi?id=4653 750 */ 751 @safe pure 752 template assertOp(string op) 753 { 754 void assertOp(T, U)(T lhs, U rhs, lazy string msg = null, 755 string file = __FILE__, 756 size_t line = __LINE__) 757 { 758 mixin("if (lhs " ~ op ~ " rhs) return;"); 759 760 string header = (msg.empty) ? null : msg ~ "; "; 761 762 fail(format("%scondition (%s %s %s) not satisfied", 763 header, lhs.repr, op, rhs.repr), 764 file, line); 765 } 766 } 767 768 /// 769 @safe pure 770 unittest 771 { 772 assertLessThan(2, 3); 773 774 auto exception = expectThrows!AssertException(assertGreaterThanOrEqual(2, 3)); 775 776 assertEquals("condition (2 >= 3) not satisfied", exception.msg); 777 } 778 779 /// 780 @safe pure 781 unittest 782 { 783 assertIn("foo", ["foo": "bar"]); 784 785 auto exception = expectThrows!AssertException(assertNotIn("foo", ["foo": "bar"])); 786 787 assertEquals(`condition ("foo" !in ["foo":"bar"]) not satisfied`, exception.msg); 788 } 789 790 791 /** 792 * Checks a probe until the timeout expires. The assert error is produced 793 * if the probe fails to return 'true' before the timeout. 794 * 795 * The parameter timeout determines the maximum timeout to wait before 796 * asserting a failure (default is 500ms). 797 * 798 * The parameter delay determines how often the predicate will be 799 * checked (default is 10ms). 800 * 801 * This kind of assertion is very useful to check on code that runs in another 802 * thread. For instance, the thread that listens to a socket. 803 * 804 * Throws: AssertException when the probe fails to become true before timeout 805 */ 806 @trusted 807 public static void assertEventually(bool delegate() probe, 808 Duration timeout = 500.msecs, Duration delay = 10.msecs, 809 lazy string msg = null, 810 string file = __FILE__, 811 size_t line = __LINE__) 812 { 813 const startTime = TickDuration.currSystemTick(); 814 815 while (!probe()) 816 { 817 const elapsedTime = cast(Duration)(TickDuration.currSystemTick() - startTime); 818 819 if (elapsedTime >= timeout) 820 fail(msg.empty ? "timed out" : msg, file, line); 821 822 Thread.sleep(delay); 823 } 824 } 825 826 827 /// 828 @safe /* pure */ 829 @("Assertation: assertEventually") 830 unittest 831 { 832 assertEventually({ static count = 0; return ++count > 23; }); 833 834 auto exception = expectThrows!AssertException(assertEventually({ return false; })); 835 836 assertEquals("timed out", exception.msg); 837 } 838 839 840 private string repr(T)(T value) 841 { 842 // format string key with double quotes 843 return format("%(%s%)", value.only); 844 }