1 // Copyright Juan Manuel Cabo 2012. 2 // Copyright Mario Kröplin 2017. 3 // Distributed under the Boost Software License, Version 1.0. 4 // (See accompanying file LICENSE_1_0.txt or copy at 5 // http://www.boost.org/LICENSE_1_0.txt) 6 7 module dunit.assertion; 8 9 import core.thread; 10 import core.time; 11 import std.algorithm; 12 import std.array; 13 import std.conv; 14 import std.range; 15 import std..string; 16 import std.traits; 17 18 /** 19 * Thrown on an assertion failure. 20 */ 21 class AssertException : Exception 22 { 23 @safe pure nothrow this(string msg, 24 string file = __FILE__, 25 size_t line = __LINE__, 26 Throwable next = null) 27 { 28 super(msg.empty ? "Assertion failure" : msg, file, line, next); 29 } 30 } 31 32 /** 33 * Thrown on an assertion failure. 34 */ 35 class AssertAllException : AssertException 36 { 37 private AssertException[] exceptions; 38 39 @safe pure nothrow this(AssertException[] exceptions, 40 string file = __FILE__, 41 size_t line = __LINE__, 42 Throwable next = null) 43 { 44 string msg = heading(exceptions.length); 45 46 exceptions.each!(exception => msg ~= '\n' ~ exception.description); 47 this.exceptions = exceptions; 48 super(msg, file, line, next); 49 } 50 51 private @safe pure nothrow static string heading(size_t count) 52 { 53 if (count == 1) 54 return "1 assertion failure:"; 55 else 56 return text(count, " assertion failures:"); 57 } 58 } 59 60 /** 61 * Returns a description of the throwable. 62 */ 63 @safe pure nothrow string description(Throwable throwable) 64 { 65 import std.path : baseName, buildPath; 66 67 with (throwable) 68 { 69 if (file.empty) 70 return text(typeid(throwable).name, ": ", msg); 71 else if (file.baseName == file) // "foo.d:42" is not rendered as link 72 return text(buildPath(".", file), ":", line, " ", typeid(throwable).name, ": ", msg); 73 else 74 return text(file, ":", line, " ", typeid(throwable).name, ": ", msg); 75 } 76 } 77 78 /** 79 * Writes the optional trace info of a throwable. 80 */ 81 void description(Output)(auto ref Output output, Throwable.TraceInfo traceInfo) 82 { 83 if (traceInfo !is null) 84 { 85 output.put("----------------\n"); 86 foreach (line; traceInfo) 87 { 88 output.put(line); 89 output.put("\n"); 90 } 91 output.put("----------------\n"); 92 } 93 } 94 95 /** 96 * Asserts that a condition is true. 97 * Throws: AssertException otherwise 98 */ 99 void assertTrue(T)(T condition, lazy string msg = null, 100 string file = __FILE__, 101 size_t line = __LINE__) 102 { 103 if (cast(bool) condition) 104 return; 105 106 fail(msg, file, line); 107 } 108 109 /// 110 @safe pure unittest 111 { 112 assertTrue(true); 113 assertTrue("foo" in ["foo": "bar"]); 114 115 auto exception = expectThrows!AssertException(assertTrue(false)); 116 117 assertEquals("Assertion failure", exception.msg); 118 } 119 120 /** 121 * Asserts that a condition is false. 122 * Throws: AssertException otherwise 123 */ 124 void assertFalse(T)(T condition, lazy string msg = null, 125 string file = __FILE__, 126 size_t line = __LINE__) 127 { 128 if (!cast(bool) condition) 129 return; 130 131 fail(msg, file, line); 132 } 133 134 /// 135 @safe pure unittest 136 { 137 assertFalse(false); 138 assertFalse("foo" in ["bar": "foo"]); 139 140 auto exception = expectThrows!AssertException(assertFalse(true)); 141 142 assertEquals("Assertion failure", exception.msg); 143 } 144 145 /** 146 * Asserts that the string values are equal. 147 * Throws: AssertException otherwise 148 */ 149 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 150 string file = __FILE__, 151 size_t line = __LINE__) 152 if (isSomeString!T) 153 { 154 import dunit.diff : description; 155 156 if (expected == actual) 157 return; 158 159 string header = (msg.empty) ? null : msg ~ "; "; 160 161 fail(header ~ description(expected.to!string, actual.to!string), 162 file, line); 163 } 164 165 /// 166 @safe pure unittest 167 { 168 assertEquals("foo", "foo"); 169 170 auto exception = expectThrows!AssertException(assertEquals("bar", "baz")); 171 172 assertEquals("expected: <ba<r>> but was: <ba<z>>", exception.msg); 173 } 174 175 /** 176 * Asserts that the floating-point values are approximately equal. 177 * Throws: AssertException otherwise 178 */ 179 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 180 string file = __FILE__, 181 size_t line = __LINE__) 182 if (isFloatingPoint!T || isFloatingPoint!U) 183 { 184 import std.math : isClose; 185 186 // keep defaults of deprecated approxEqual 187 if (isClose(expected, actual, 1e-2, 1e-5)) 188 return; 189 190 string header = (msg.empty) ? null : msg ~ "; "; 191 192 fail(header ~ format("expected: <%s> but was: <%s>", expected, actual), 193 file, line); 194 } 195 196 /// 197 @safe /*pure*/ unittest // format is impure for floating point values 198 { 199 assertEquals(1, 1.01); 200 201 auto exception = expectThrows!AssertException(assertEquals(1, 1.1)); 202 203 assertEquals("expected: <1> but was: <1.1>", exception.msg); 204 } 205 206 /** 207 * Asserts that the values are equal. 208 * Throws: AssertException otherwise 209 */ 210 void assertEquals(T, U)(T expected, U actual, lazy string msg = null, 211 string file = __FILE__, 212 size_t line = __LINE__) 213 if (!isSomeString!T && !isFloatingPoint!T && !isFloatingPoint!U) 214 { 215 if (expected == actual) 216 return; 217 218 string header = (msg.empty) ? null : msg ~ "; "; 219 220 fail(header ~ format("expected: <%s> but was: <%s>", expected, actual), 221 file, line); 222 } 223 224 /// 225 @safe pure unittest 226 { 227 assertEquals(42, 42); 228 229 auto exception = expectThrows!AssertException(assertEquals(42, 24)); 230 231 assertEquals("expected: <42> but was: <24>", exception.msg); 232 } 233 234 /// 235 unittest // Object.opEquals is impure 236 { 237 Object foo = new Object(); 238 Object bar = null; 239 240 assertEquals(foo, foo); 241 assertEquals(bar, bar); 242 243 auto exception = expectThrows!AssertException(assertEquals(foo, bar)); 244 245 assertEquals("expected: <object.Object> but was: <null>", exception.msg); 246 } 247 248 /** 249 * Asserts that the arrays are equal. 250 * Throws: AssertException otherwise 251 */ 252 void assertArrayEquals(T, U)(in T[] expected, in U[] actual, lazy string msg = null, 253 string file = __FILE__, 254 size_t line = __LINE__) 255 { 256 assertRangeEquals(expected, actual, 257 msg, 258 file, line); 259 } 260 261 /** 262 * Asserts that the associative arrays are equal. 263 * Throws: AssertException otherwise 264 */ 265 void assertArrayEquals(T, U, V)(in T[V] expected, in U[V] actual, lazy string msg = null, 266 string file = __FILE__, 267 size_t line = __LINE__) 268 { 269 string header = (msg.empty) ? null : msg ~ "; "; 270 271 foreach (key; expected.byKey) 272 if (key in actual) 273 { 274 assertEquals(expected[key], actual[key], 275 format(header ~ "mismatch at key %s", key.repr), 276 file, line); 277 } 278 279 auto difference = setSymmetricDifference(expected.keys.sort(), actual.keys.sort()); 280 281 assertEmpty(difference, 282 format("key mismatch; difference: %(%s, %)", difference), 283 file, line); 284 } 285 286 /// 287 pure unittest // keys, values, byKey, byValue not usable in @safe context 288 { 289 int[string] expected = ["foo": 1, "bar": 2]; 290 291 assertArrayEquals(expected, ["foo": 1, "bar": 2]); 292 293 AssertException exception; 294 295 exception = expectThrows!AssertException(assertArrayEquals(expected, ["foo": 2])); 296 assertEquals(`mismatch at key "foo"; expected: <1> but was: <2>`, exception.msg); 297 exception = expectThrows!AssertException(assertArrayEquals(expected, ["foo": 1])); 298 assertEquals(`key mismatch; difference: "bar"`, exception.msg); 299 } 300 301 /** 302 * Asserts that the ranges are equal. 303 * Throws: AssertException otherwise 304 */ 305 void assertRangeEquals(R1, R2)(R1 expected, R2 actual, lazy string msg = null, 306 string file = __FILE__, 307 size_t line = __LINE__) 308 if (isInputRange!R1 && isInputRange!R2 && is(typeof(expected.front == actual.front))) 309 { 310 string header = (msg.empty) ? null : msg ~ "; "; 311 size_t index = 0; 312 313 for (; !expected.empty && ! actual.empty; ++index, expected.popFront, actual.popFront) 314 { 315 assertEquals(expected.front, actual.front, 316 header ~ format("mismatch at index %s", index), 317 file, line); 318 } 319 assertEmpty(expected, 320 header ~ format("length mismatch at index %s; ", index) ~ 321 format("expected: <%s> but was: empty", expected.front), 322 file, line); 323 assertEmpty(actual, 324 header ~ format("length mismatch at index %s; ", index) ~ 325 format("expected: empty but was: <%s>", actual.front), 326 file, line); 327 } 328 329 /// 330 @safe pure unittest 331 { 332 int[] expected = [0, 1]; 333 334 assertRangeEquals(expected, [0, 1]); 335 336 AssertException exception; 337 338 exception = expectThrows!AssertException(assertRangeEquals(expected, [0])); 339 assertEquals("length mismatch at index 1; expected: <1> but was: empty", exception.msg); 340 exception = expectThrows!AssertException(assertRangeEquals(expected, [0, 1, 2])); 341 assertEquals("length mismatch at index 2; expected: empty but was: <2>", exception.msg); 342 exception = expectThrows!AssertException(assertArrayEquals("bar", "baz")); 343 assertEquals("mismatch at index 2; expected: <r> but was: <z>", exception.msg); 344 } 345 346 /** 347 * Asserts that the value is empty. 348 * Throws: AssertException otherwise 349 */ 350 void assertEmpty(T)(T actual, lazy string msg = null, 351 string file = __FILE__, 352 size_t line = __LINE__) 353 { 354 if (actual.empty) 355 return; 356 357 fail(msg, file, line); 358 } 359 360 /// 361 @safe pure unittest 362 { 363 assertEmpty([]); 364 365 auto exception = expectThrows!AssertException(assertEmpty([1, 2, 3])); 366 367 assertEquals("Assertion failure", exception.msg); 368 } 369 370 /** 371 * Asserts that the value is not empty. 372 * Throws: AssertException otherwise 373 */ 374 void assertNotEmpty(T)(T actual, lazy string msg = null, 375 string file = __FILE__, 376 size_t line = __LINE__) 377 { 378 if (!actual.empty) 379 return; 380 381 fail(msg, file, line); 382 } 383 384 /// 385 @safe pure unittest 386 { 387 assertNotEmpty([1, 2, 3]); 388 389 auto exception = expectThrows!AssertException(assertNotEmpty([])); 390 391 assertEquals("Assertion failure", exception.msg); 392 } 393 394 /** 395 * Asserts that the value is null. 396 * Throws: AssertException otherwise 397 */ 398 void assertNull(T)(T actual, lazy string msg = null, 399 string file = __FILE__, 400 size_t line = __LINE__) 401 { 402 if (actual is null) 403 return; 404 405 fail(msg, file, line); 406 } 407 408 /// 409 @safe pure unittest 410 { 411 Object foo = new Object(); 412 413 assertNull(null); 414 415 auto exception = expectThrows!AssertException(assertNull(foo)); 416 417 assertEquals("Assertion failure", exception.msg); 418 } 419 420 /** 421 * Asserts that the value is not null. 422 * Throws: AssertException otherwise 423 */ 424 void assertNotNull(T)(T actual, lazy string msg = null, 425 string file = __FILE__, 426 size_t line = __LINE__) 427 { 428 if (actual !is null) 429 return; 430 431 fail(msg, file, line); 432 } 433 434 /// 435 @safe pure unittest 436 { 437 Object foo = new Object(); 438 439 assertNotNull(foo); 440 441 auto exception = expectThrows!AssertException(assertNotNull(null)); 442 443 assertEquals("Assertion failure", exception.msg); 444 } 445 446 /** 447 * Asserts that the values are the same. 448 * Throws: AssertException otherwise 449 */ 450 void assertSame(T, U)(T expected, U actual, lazy string msg = null, 451 string file = __FILE__, 452 size_t line = __LINE__) 453 { 454 if (expected is actual) 455 return; 456 457 string header = (msg.empty) ? null : msg ~ "; "; 458 459 fail(header ~ format("expected same: <%s> was not: <%s>", expected, actual), 460 file, line); 461 } 462 463 /// 464 unittest // format is impure and not safe for Object 465 { 466 Object foo = new Object(); 467 Object bar = new Object(); 468 469 assertSame(foo, foo); 470 471 auto exception = expectThrows!AssertException(assertSame(foo, bar)); 472 473 assertEquals("expected same: <object.Object> was not: <object.Object>", exception.msg); 474 } 475 476 /** 477 * Asserts that the values are not the same. 478 * Throws: AssertException otherwise 479 */ 480 void assertNotSame(T, U)(T expected, U actual, lazy string msg = null, 481 string file = __FILE__, 482 size_t line = __LINE__) 483 { 484 if (expected !is actual) 485 return; 486 487 string header = (msg.empty) ? null : msg ~ "; "; 488 489 fail(header ~ "expected not same", 490 file, line); 491 } 492 493 /// 494 @safe pure unittest 495 { 496 Object foo = new Object(); 497 Object bar = new Object(); 498 499 assertNotSame(foo, bar); 500 501 auto exception = expectThrows!AssertException(assertNotSame(foo, foo)); 502 503 assertEquals("expected not same", exception.msg); 504 } 505 506 /** 507 * Asserts that all assertions pass. 508 * Throws: AssertAllException otherwise 509 */ 510 void assertAll(void delegate() @safe [] assertions ...) @safe 511 { 512 AssertException[] exceptions = null; 513 514 foreach (assertion; assertions) 515 try 516 assertion(); 517 catch (AssertException exception) 518 exceptions ~= exception; 519 if (!exceptions.empty) 520 { 521 // [Issue 16345] IFTI fails with lazy variadic function in some cases 522 const file = null; 523 const line = 0; 524 525 throw new AssertAllException(exceptions, file, line); 526 } 527 } 528 529 /// 530 @safe unittest 531 { 532 assertAll( 533 assertTrue(true), 534 assertFalse(false), 535 ); 536 537 auto exception = expectThrows!AssertException(assertAll( 538 assertTrue(false), 539 assertFalse(true), 540 )); 541 542 assertTrue(exception.msg.canFind("2 assertion failures"), exception.msg); 543 } 544 545 /** 546 * Asserts that the expression throws the specified throwable. 547 * Throws: AssertException otherwise 548 * Returns: the caught throwable 549 */ 550 T expectThrows(T : Throwable = Exception, E)(lazy E expression, lazy string msg = null, 551 string file = __FILE__, 552 size_t line = __LINE__) 553 { 554 try 555 expression(); 556 catch (T throwable) 557 return throwable; 558 559 string header = (msg.empty) ? null : msg ~ "; "; 560 561 fail(header ~ format("expected <%s> was not thrown", T.stringof), 562 file, line); 563 assert(0); 564 } 565 566 /// 567 @safe pure unittest 568 { 569 import std.exception : enforce; 570 571 auto exception = expectThrows(enforce(false)); 572 573 assertEquals("Enforcement failed", exception.msg); 574 } 575 576 /// 577 @safe pure unittest 578 { 579 auto exception = expectThrows!AssertException(expectThrows(42)); 580 581 assertEquals("expected <Exception> was not thrown", exception.msg); 582 } 583 584 /** 585 * Fails a test. 586 * Throws: AssertException 587 */ 588 void fail(string msg = null, 589 string file = __FILE__, 590 size_t line = __LINE__) @safe pure 591 { 592 throw new AssertException(msg, file, line); 593 } 594 595 /// 596 @safe pure unittest 597 { 598 auto exception = expectThrows!AssertException(fail()); 599 600 assertEquals("Assertion failure", exception.msg); 601 } 602 603 alias assertGreaterThan = assertOp!">"; 604 alias assertGreaterThanOrEqual = assertOp!">="; 605 alias assertLessThan = assertOp!"<"; 606 alias assertLessThanOrEqual = assertOp!"<="; 607 alias assertIn = assertOp!"in"; 608 alias assertNotIn = assertOp!"!in"; 609 610 /** 611 * Asserts that the condition (lhs op rhs) is satisfied. 612 * Throws: AssertException otherwise 613 * See_Also: http://d.puremagic.com/issues/show_bug.cgi?id=4653 614 */ 615 template assertOp(string op) 616 { 617 void assertOp(T, U)(T lhs, U rhs, lazy string msg = null, 618 string file = __FILE__, 619 size_t line = __LINE__) 620 { 621 mixin("if (lhs " ~ op ~ " rhs) return;"); 622 623 string header = (msg.empty) ? null : msg ~ "; "; 624 625 fail(format("%scondition (%s %s %s) not satisfied", 626 header, lhs.repr, op, rhs.repr), 627 file, line); 628 } 629 } 630 631 /// 632 @safe pure unittest 633 { 634 assertLessThan(2, 3); 635 636 auto exception = expectThrows!AssertException(assertGreaterThanOrEqual(2, 3)); 637 638 assertEquals("condition (2 >= 3) not satisfied", exception.msg); 639 } 640 641 /// 642 @safe pure unittest 643 { 644 assertIn("foo", ["foo": "bar"]); 645 646 auto exception = expectThrows!AssertException(assertNotIn("foo", ["foo": "bar"])); 647 648 assertEquals(`condition ("foo" !in ["foo":"bar"]) not satisfied`, exception.msg); 649 } 650 651 /** 652 * Checks a probe until the timeout expires. The assert error is produced 653 * if the probe fails to return 'true' before the timeout. 654 * 655 * The parameter timeout determines the maximum timeout to wait before 656 * asserting a failure (default is 500ms). 657 * 658 * The parameter delay determines how often the predicate will be 659 * checked (default is 10ms). 660 * 661 * This kind of assertion is very useful to check on code that runs in another 662 * thread. For instance, the thread that listens to a socket. 663 * 664 * Throws: AssertException when the probe fails to become true before timeout 665 */ 666 public static void assertEventually(bool delegate() probe, 667 Duration timeout = 500.msecs, Duration delay = 10.msecs, 668 lazy string msg = null, 669 string file = __FILE__, 670 size_t line = __LINE__) 671 { 672 const startTime = TickDuration.currSystemTick(); 673 674 while (!probe()) 675 { 676 const elapsedTime = cast(Duration)(TickDuration.currSystemTick() - startTime); 677 678 if (elapsedTime >= timeout) 679 fail(msg.empty ? "timed out" : msg, file, line); 680 681 Thread.sleep(delay); 682 } 683 } 684 685 /// 686 unittest 687 { 688 assertEventually({ static count = 0; return ++count > 23; }); 689 690 auto exception = expectThrows!AssertException(assertEventually({ return false; })); 691 692 assertEquals("timed out", exception.msg); 693 } 694 695 private string repr(T)(T value) 696 { 697 // format string key with double quotes 698 return format("%(%s%)", value.only); 699 }